diff --git a/L2J_Mobius_01.0_Ertheia/dist/game/config/General.ini b/L2J_Mobius_01.0_Ertheia/dist/game/config/General.ini index 0df1f9064e..161bb136e8 100644 --- a/L2J_Mobius_01.0_Ertheia/dist/game/config/General.ini +++ b/L2J_Mobius_01.0_Ertheia/dist/game/config/General.ini @@ -290,7 +290,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_01.0_Ertheia/dist/game/config/GeoEngine.ini b/L2J_Mobius_01.0_Ertheia/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_01.0_Ertheia/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_01.0_Ertheia/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_01.0_Ertheia/dist/game/data/geodata/Readme.txt b/L2J_Mobius_01.0_Ertheia/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_01.0_Ertheia/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_01.0_Ertheia/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/targethandlers/Ground.java index 0d22bf85ce..5ac9e1e0de 100644 --- a/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_01.0_Ertheia/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/Config.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/Config.java index 71f0d76c20..af0d32a9e0 100644 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -944,18 +943,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2480,19 +2477,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java similarity index 52% rename from L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java rename to L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java index f77d21d84b..f0296ff201 100644 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -14,55 +14,44 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.l2jmobius.gameserver.geoengine.geodata; +package org.l2jmobius.gameserver.geoengine.geodata.regions; -public class BlockNull extends ABlock +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion { + public static final NullRegion INSTANCE = new NullRegion(); + @Override - public boolean hasGeoPos() + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() { return false; } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java similarity index 66% rename from L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/enums/GeoType.java rename to L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java index f77aa5eac4..7ef07ab22c 100644 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/enums/GeoType.java +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -14,22 +14,22 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.l2jmobius.gameserver.enums; +package org.l2jmobius.gameserver.geoengine.pathfinding; -public enum GeoType +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc { - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); + public abstract int getX(); - private final String _filename; + public abstract int getY(); - private GeoType(String filename) - { - _filename = filename; - } + public abstract int getZ(); - public String getFilename() - { - return _filename; - } -} \ No newline at end of file + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/model/actor/Creature.java index ab273c4937..586d86cfa2 100644 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3465,7 +3467,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3509,7 +3511,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3553,7 +3555,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_02.5_Underground/dist/game/config/General.ini b/L2J_Mobius_02.5_Underground/dist/game/config/General.ini index a5cbf4242e..d9539820d8 100644 --- a/L2J_Mobius_02.5_Underground/dist/game/config/General.ini +++ b/L2J_Mobius_02.5_Underground/dist/game/config/General.ini @@ -298,7 +298,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_02.5_Underground/dist/game/config/GeoEngine.ini b/L2J_Mobius_02.5_Underground/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_02.5_Underground/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_02.5_Underground/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_02.5_Underground/dist/game/data/geodata/Readme.txt b/L2J_Mobius_02.5_Underground/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_02.5_Underground/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_02.5_Underground/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/targethandlers/Ground.java index 0d22bf85ce..5ac9e1e0de 100644 --- a/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_02.5_Underground/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/Config.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/Config.java index 39b99a2068..c85f4a572e 100644 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -954,18 +953,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2503,19 +2500,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java similarity index 52% rename from L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java rename to L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java index f77d21d84b..f0296ff201 100644 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -14,55 +14,44 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.l2jmobius.gameserver.geoengine.geodata; +package org.l2jmobius.gameserver.geoengine.geodata.regions; -public class BlockNull extends ABlock +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion { + public static final NullRegion INSTANCE = new NullRegion(); + @Override - public boolean hasGeoPos() + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() { return false; } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java similarity index 66% rename from L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/enums/GeoType.java rename to L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java index f77aa5eac4..7ef07ab22c 100644 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/enums/GeoType.java +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -14,22 +14,22 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.l2jmobius.gameserver.enums; +package org.l2jmobius.gameserver.geoengine.pathfinding; -public enum GeoType +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc { - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); + public abstract int getX(); - private final String _filename; + public abstract int getY(); - private GeoType(String filename) - { - _filename = filename; - } + public abstract int getZ(); - public String getFilename() - { - return _filename; - } -} \ No newline at end of file + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/model/actor/Creature.java index ab273c4937..586d86cfa2 100644 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3465,7 +3467,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3509,7 +3511,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3553,7 +3555,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_03.0_Helios/dist/game/config/General.ini b/L2J_Mobius_03.0_Helios/dist/game/config/General.ini index a5cbf4242e..d9539820d8 100644 --- a/L2J_Mobius_03.0_Helios/dist/game/config/General.ini +++ b/L2J_Mobius_03.0_Helios/dist/game/config/General.ini @@ -298,7 +298,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_03.0_Helios/dist/game/config/GeoEngine.ini b/L2J_Mobius_03.0_Helios/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_03.0_Helios/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_03.0_Helios/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_03.0_Helios/dist/game/data/geodata/Readme.txt b/L2J_Mobius_03.0_Helios/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_03.0_Helios/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_03.0_Helios/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/targethandlers/Ground.java index 0d22bf85ce..5ac9e1e0de 100644 --- a/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_03.0_Helios/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/Config.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/Config.java index 4283c6e282..1be266e03f 100644 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -967,18 +966,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2525,19 +2522,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java similarity index 52% rename from L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java rename to L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java index f77d21d84b..f0296ff201 100644 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -14,55 +14,44 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.l2jmobius.gameserver.geoengine.geodata; +package org.l2jmobius.gameserver.geoengine.geodata.regions; -public class BlockNull extends ABlock +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion { + public static final NullRegion INSTANCE = new NullRegion(); + @Override - public boolean hasGeoPos() + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() { return false; } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java similarity index 66% rename from L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/enums/GeoType.java rename to L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java index f77aa5eac4..7ef07ab22c 100644 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/enums/GeoType.java +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -14,22 +14,22 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.l2jmobius.gameserver.enums; +package org.l2jmobius.gameserver.geoengine.pathfinding; -public enum GeoType +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc { - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); + public abstract int getX(); - private final String _filename; + public abstract int getY(); - private GeoType(String filename) - { - _filename = filename; - } + public abstract int getZ(); - public String getFilename() - { - return _filename; - } -} \ No newline at end of file + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/model/actor/Creature.java index ab273c4937..586d86cfa2 100644 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3465,7 +3467,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3509,7 +3511,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3553,7 +3555,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_04.0_GrandCrusade/dist/game/config/General.ini b/L2J_Mobius_04.0_GrandCrusade/dist/game/config/General.ini index a5cbf4242e..d9539820d8 100644 --- a/L2J_Mobius_04.0_GrandCrusade/dist/game/config/General.ini +++ b/L2J_Mobius_04.0_GrandCrusade/dist/game/config/General.ini @@ -298,7 +298,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_04.0_GrandCrusade/dist/game/config/GeoEngine.ini b/L2J_Mobius_04.0_GrandCrusade/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_04.0_GrandCrusade/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_04.0_GrandCrusade/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_04.0_GrandCrusade/dist/game/data/geodata/Readme.txt b/L2J_Mobius_04.0_GrandCrusade/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_04.0_GrandCrusade/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_04.0_GrandCrusade/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/targethandlers/Ground.java index 7e62d76609..67d391ff2a 100644 --- a/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_04.0_GrandCrusade/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/Config.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/Config.java index 470fc441be..c2905936bc 100644 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -954,18 +953,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2499,19 +2496,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java similarity index 52% rename from L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java rename to L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java index f77d21d84b..f0296ff201 100644 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -14,55 +14,44 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.l2jmobius.gameserver.geoengine.geodata; +package org.l2jmobius.gameserver.geoengine.geodata.regions; -public class BlockNull extends ABlock +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion { + public static final NullRegion INSTANCE = new NullRegion(); + @Override - public boolean hasGeoPos() + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() { return false; } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java similarity index 66% rename from L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/enums/GeoType.java rename to L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java index f77aa5eac4..7ef07ab22c 100644 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/enums/GeoType.java +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -14,22 +14,22 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.l2jmobius.gameserver.enums; +package org.l2jmobius.gameserver.geoengine.pathfinding; -public enum GeoType +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc { - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); + public abstract int getX(); - private final String _filename; + public abstract int getY(); - private GeoType(String filename) - { - _filename = filename; - } + public abstract int getZ(); - public String getFilename() - { - return _filename; - } -} \ No newline at end of file + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/model/actor/Creature.java index ab273c4937..586d86cfa2 100644 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3465,7 +3467,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3509,7 +3511,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3553,7 +3555,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_05.0_Salvation/dist/game/config/General.ini b/L2J_Mobius_05.0_Salvation/dist/game/config/General.ini index a5cbf4242e..d9539820d8 100644 --- a/L2J_Mobius_05.0_Salvation/dist/game/config/General.ini +++ b/L2J_Mobius_05.0_Salvation/dist/game/config/General.ini @@ -298,7 +298,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_05.0_Salvation/dist/game/config/GeoEngine.ini b/L2J_Mobius_05.0_Salvation/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_05.0_Salvation/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_05.0_Salvation/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_05.0_Salvation/dist/game/data/geodata/Readme.txt b/L2J_Mobius_05.0_Salvation/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_05.0_Salvation/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_05.0_Salvation/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/targethandlers/Ground.java index 7e62d76609..67d391ff2a 100644 --- a/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_05.0_Salvation/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/Config.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/Config.java index bc6effb2b9..6dceba1a20 100644 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -953,18 +952,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2508,19 +2505,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/model/actor/Creature.java index c0d076c704..7eab69ed43 100644 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3465,7 +3467,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3509,7 +3511,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3553,7 +3555,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_05.5_EtinasFate/dist/game/config/General.ini b/L2J_Mobius_05.5_EtinasFate/dist/game/config/General.ini index a5cbf4242e..d9539820d8 100644 --- a/L2J_Mobius_05.5_EtinasFate/dist/game/config/General.ini +++ b/L2J_Mobius_05.5_EtinasFate/dist/game/config/General.ini @@ -298,7 +298,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_05.5_EtinasFate/dist/game/config/GeoEngine.ini b/L2J_Mobius_05.5_EtinasFate/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_05.5_EtinasFate/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_05.5_EtinasFate/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_05.5_EtinasFate/dist/game/data/geodata/Readme.txt b/L2J_Mobius_05.5_EtinasFate/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_05.5_EtinasFate/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_05.5_EtinasFate/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/targethandlers/Ground.java index 7e62d76609..67d391ff2a 100644 --- a/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_05.5_EtinasFate/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/Config.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/Config.java index 1b66082860..80682a7d7a 100644 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -960,18 +959,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2520,19 +2517,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/model/actor/Creature.java index c0d076c704..7eab69ed43 100644 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3465,7 +3467,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3509,7 +3511,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3553,7 +3555,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_06.0_Fafurion/dist/game/config/General.ini b/L2J_Mobius_06.0_Fafurion/dist/game/config/General.ini index a5cbf4242e..d9539820d8 100644 --- a/L2J_Mobius_06.0_Fafurion/dist/game/config/General.ini +++ b/L2J_Mobius_06.0_Fafurion/dist/game/config/General.ini @@ -298,7 +298,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_06.0_Fafurion/dist/game/config/GeoEngine.ini b/L2J_Mobius_06.0_Fafurion/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_06.0_Fafurion/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_06.0_Fafurion/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_06.0_Fafurion/dist/game/data/geodata/Readme.txt b/L2J_Mobius_06.0_Fafurion/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_06.0_Fafurion/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_06.0_Fafurion/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/targethandlers/Ground.java index 7e62d76609..67d391ff2a 100644 --- a/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_06.0_Fafurion/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/Config.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/Config.java index 6aa023a547..0434310da7 100644 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -972,18 +971,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2563,19 +2560,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/model/actor/Creature.java index c0d076c704..7eab69ed43 100644 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3465,7 +3467,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3509,7 +3511,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3553,7 +3555,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_07.0_PreludeOfWar/dist/game/config/General.ini b/L2J_Mobius_07.0_PreludeOfWar/dist/game/config/General.ini index 533eedf234..472a30f2a8 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/dist/game/config/General.ini +++ b/L2J_Mobius_07.0_PreludeOfWar/dist/game/config/General.ini @@ -299,7 +299,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_07.0_PreludeOfWar/dist/game/config/GeoEngine.ini b/L2J_Mobius_07.0_PreludeOfWar/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_07.0_PreludeOfWar/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/geodata/Readme.txt b/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/targethandlers/Ground.java index 7e62d76609..67d391ff2a 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_07.0_PreludeOfWar/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/Config.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/Config.java index 19ab3a0e51..bc4bbd90cd 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -980,18 +979,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2579,19 +2576,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/actor/Creature.java index aca335f8ca..b8e44725c2 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3464,7 +3466,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3508,7 +3510,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3552,7 +3554,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_08.2_Homunculus/dist/game/config/General.ini b/L2J_Mobius_08.2_Homunculus/dist/game/config/General.ini index 0cd8f228e0..33c41c47f0 100644 --- a/L2J_Mobius_08.2_Homunculus/dist/game/config/General.ini +++ b/L2J_Mobius_08.2_Homunculus/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_08.2_Homunculus/dist/game/config/GeoEngine.ini b/L2J_Mobius_08.2_Homunculus/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_08.2_Homunculus/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_08.2_Homunculus/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_08.2_Homunculus/dist/game/data/geodata/Readme.txt b/L2J_Mobius_08.2_Homunculus/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_08.2_Homunculus/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_08.2_Homunculus/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/targethandlers/Ground.java index 7e62d76609..67d391ff2a 100644 --- a/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_08.2_Homunculus/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/Config.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/Config.java index b793f7d10f..8ac79f407d 100644 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -972,18 +971,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2555,19 +2552,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/model/actor/Creature.java index 69b1ba2210..5765709e4b 100644 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3464,7 +3466,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3508,7 +3510,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3552,7 +3554,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/config/General.ini b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/config/General.ini index 0cd8f228e0..33c41c47f0 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/config/General.ini +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/config/GeoEngine.ini b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/geodata/Readme.txt b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/targethandlers/Ground.java index 7e62d76609..67d391ff2a 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/Config.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/Config.java index b27f9af46b..e0d6f32452 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -983,18 +982,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2591,19 +2588,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/model/actor/Creature.java index 69b1ba2210..5765709e4b 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3464,7 +3466,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3508,7 +3510,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3552,7 +3554,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_10.1_MasterClass/dist/game/config/General.ini b/L2J_Mobius_10.1_MasterClass/dist/game/config/General.ini index 0cd8f228e0..33c41c47f0 100644 --- a/L2J_Mobius_10.1_MasterClass/dist/game/config/General.ini +++ b/L2J_Mobius_10.1_MasterClass/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_10.1_MasterClass/dist/game/config/GeoEngine.ini b/L2J_Mobius_10.1_MasterClass/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_10.1_MasterClass/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_10.1_MasterClass/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_10.1_MasterClass/dist/game/data/geodata/Readme.txt b/L2J_Mobius_10.1_MasterClass/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_10.1_MasterClass/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_10.1_MasterClass/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/targethandlers/Ground.java index 7e62d76609..67d391ff2a 100644 --- a/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_10.1_MasterClass/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/Config.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/Config.java index 8cf9944857..e4bca5526a 100644 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -983,18 +982,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2591,19 +2588,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/model/actor/Creature.java index f3071791bb..c8088deee8 100644 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -66,6 +66,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -820,7 +822,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2687,7 +2689,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3463,7 +3465,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3507,7 +3509,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3551,7 +3553,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_10.1_MasterClass/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_10.2_MasterClass/dist/game/config/General.ini b/L2J_Mobius_10.2_MasterClass/dist/game/config/General.ini index 0cd8f228e0..33c41c47f0 100644 --- a/L2J_Mobius_10.2_MasterClass/dist/game/config/General.ini +++ b/L2J_Mobius_10.2_MasterClass/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_10.2_MasterClass/dist/game/config/GeoEngine.ini b/L2J_Mobius_10.2_MasterClass/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_10.2_MasterClass/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_10.2_MasterClass/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_10.2_MasterClass/dist/game/data/geodata/Readme.txt b/L2J_Mobius_10.2_MasterClass/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_10.2_MasterClass/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_10.2_MasterClass/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/targethandlers/Ground.java index 7e62d76609..67d391ff2a 100644 --- a/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_10.2_MasterClass/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/Config.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/Config.java index 8cf9944857..e4bca5526a 100644 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -983,18 +982,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2591,19 +2588,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/model/actor/Creature.java index f3071791bb..c8088deee8 100644 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -66,6 +66,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -820,7 +822,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2687,7 +2689,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3463,7 +3465,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3507,7 +3509,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3551,7 +3553,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_C4_ScionsOfDestiny/dist/game/config/General.ini b/L2J_Mobius_C4_ScionsOfDestiny/dist/game/config/General.ini index 10c097bd3f..496802aeba 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/dist/game/config/General.ini +++ b/L2J_Mobius_C4_ScionsOfDestiny/dist/game/config/General.ini @@ -472,6 +472,7 @@ AltVillagesRepQuestReward = False # --------------------------------------------------------------------------- AllowLottery = True AllowRace = True +AllowWater = True AllowRentPet = False AllowFishing = True AllowBoat = True @@ -479,6 +480,15 @@ AllowManor = True AllowNpcWalkers = True +# --------------------------------------------------------------------------- +# Falling Damage +# --------------------------------------------------------------------------- + +# Allow characters to receive damage from falling. +# Default: True +EnableFallingDamage = True + + # --------------------------------------------------------------------------- # Server Optimization # --------------------------------------------------------------------------- diff --git a/L2J_Mobius_C4_ScionsOfDestiny/dist/game/config/GeoEngine.ini b/L2J_Mobius_C4_ScionsOfDestiny/dist/game/config/GeoEngine.ini index 5889c6caf0..dff362b8ff 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_C4_ScionsOfDestiny/dist/game/config/GeoEngine.ini @@ -2,51 +2,50 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False # ================================================================= # Other diff --git a/L2J_Mobius_C4_ScionsOfDestiny/dist/game/data/geodata/Readme.txt b/L2J_Mobius_C4_ScionsOfDestiny/dist/game/data/geodata/Readme.txt index 1e29544602..2919d7782d 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_C4_ScionsOfDestiny/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/main/GeoEngine.ini" with your favorite text editor and then edit following configs: - - GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" - - GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_C4_ScionsOfDestiny/dist/game/data/scripts/ai/bosses/Antharas.java b/L2J_Mobius_C4_ScionsOfDestiny/dist/game/data/scripts/ai/bosses/Antharas.java index 45dbb4314b..315d71ad2f 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/dist/game/data/scripts/ai/bosses/Antharas.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/dist/game/data/scripts/ai/bosses/Antharas.java @@ -581,7 +581,7 @@ public class Antharas extends Quest final int ry = getRandom(112400, 116000); final int rdt = ((_antharas.getX() - rx) * (_antharas.getX() - rx)) + ((_antharas.getY() - ry) * (_antharas.getY() - ry)); final Location randomLocation = new Location(rx, ry, -7704); - if (GeoEngine.getInstance().canSeeLocation(_antharas, randomLocation) && (rdt < dt)) + if (GeoEngine.getInstance().canSeeTarget(_antharas, randomLocation) && (rdt < dt)) { x = rx; y = ry; diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/Config.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/Config.java index 7636b84489..74d77482dc 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/Config.java @@ -40,7 +40,6 @@ import org.l2jmobius.commons.enums.ServerMode; import org.l2jmobius.commons.util.ClassMasterSettings; import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; import org.l2jmobius.gameserver.model.olympiad.OlympiadPeriod; @@ -165,9 +164,11 @@ public class Config public static boolean ALT_VILLAGES_REPEATABLE_QUEST_REWARD; public static boolean ALLOW_LOTTERY; public static boolean ALLOW_RACE; + public static boolean ALLOW_WATER; public static boolean ALLOW_RENTPET; public static boolean ALLOW_BOAT; public static boolean ALLOW_NPC_WALKERS; + public static boolean ENABLE_FALLING_DAMAGE; public static int MIN_NPC_ANIMATION; public static int MAX_NPC_ANIMATION; public static int MIN_MONSTER_ANIMATION; @@ -866,20 +867,16 @@ public class Config public static boolean ALT_RAIDS_STATS_BONUS; public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; - public static boolean FALL_DAMAGE; - public static boolean ALLOW_WATER; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; public static int RBLOCKRAGE; public static boolean PLAYERS_CAN_HEAL_RB; @@ -1457,12 +1454,14 @@ public class Config ALT_VILLAGES_REPEATABLE_QUEST_REWARD = generalConfig.getBoolean("AltVillagesRepQuestReward", false); ALLOW_LOTTERY = generalConfig.getBoolean("AllowLottery", false); ALLOW_RACE = generalConfig.getBoolean("AllowRace", false); + ALLOW_WATER = generalConfig.getBoolean("AllowWater", false); ALLOW_RENTPET = generalConfig.getBoolean("AllowRentPet", false); ALLOW_DISCARDITEM = generalConfig.getBoolean("AllowDiscardItem", true); ALLOWFISHING = generalConfig.getBoolean("AllowFishing", false); ALLOW_MANOR = generalConfig.getBoolean("AllowManor", false); ALLOW_BOAT = generalConfig.getBoolean("AllowBoat", false); ALLOW_NPC_WALKERS = generalConfig.getBoolean("AllowNpcWalkers", true); + ENABLE_FALLING_DAMAGE = generalConfig.getBoolean("EnableFallingDamage", true); DEFAULT_GLOBAL_CHAT = generalConfig.getString("GlobalChat", "ON"); DEFAULT_TRADE_CHAT = generalConfig.getString("TradeChat", "ON"); MAX_CHAT_LENGTH = generalConfig.getInt("MaxChatLength", 100); @@ -2397,22 +2396,18 @@ public class Config public static void loadgeodataConfig() { - final PropertiesParser geoengineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoengineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoengineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoengineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoengineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoengineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoengineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoengineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoengineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoengineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoengineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoengineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoengineConfig.getInt("MaxObstacleHeight", 32); - FALL_DAMAGE = geoengineConfig.getBoolean("FallDamage", false); - ALLOW_WATER = geoengineConfig.getBoolean("AllowWater", false); + final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); } public static void loadBossConfig() diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/ai/AttackableAI.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/ai/AttackableAI.java index b92c551d05..60481ef79d 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/ai/AttackableAI.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/ai/AttackableAI.java @@ -792,7 +792,7 @@ public class AttackableAI extends CreatureAI if (!_actor.isInsideRadius2D(newX, newY, originalAttackTarget.getZ(), collision)) { final int newZ = _actor.getZ() + 30; - if (!Config.PATHFINDING || GeoEngine.getInstance().canMoveToTarget(_actor.getX(), _actor.getY(), _actor.getZ(), newX, newY, newZ, _actor.getInstanceId())) + if ((Config.PATHFINDING <= 0) || GeoEngine.getInstance().canMoveToTarget(_actor.getX(), _actor.getY(), _actor.getZ(), newX, newY, newZ, _actor.getInstanceId())) { moveTo(newX, newY, newZ); } diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/ai/SiegeGuardAI.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/ai/SiegeGuardAI.java index 216ce4532f..13fb1ad0e3 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/ai/SiegeGuardAI.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/ai/SiegeGuardAI.java @@ -605,7 +605,7 @@ public class SiegeGuardAI extends CreatureAI implements Runnable // Check if the WorldObject is inside the Faction Range of the actor if ((npc.getAI() != null) && ((npc.getAI().getIntention() == AI_INTENTION_IDLE) || (npc.getAI().getIntention() == AI_INTENTION_ACTIVE)) && actor.isInsideRadius2D(npc, npc.getFactionRange()) && target.isInsideRadius2D(npc, npc.getFactionRange())) { - if (Config.PATHFINDING) + if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(npc, target)) { diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index cdcb4ec7e2..a96dbcf0e7 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1062 +16,580 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; -import org.l2jmobius.gameserver.util.Util; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceId(), target.getX(), target.getY(), target.getZ(), target.getInstanceId())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, Location worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceId()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instanceId + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstanceId the target's instance id + * @return + */ + public boolean canSeeTarget(int x, int y, int z, int instanceId, int tx, int ty, int tz, int tInstanceId) + { + return (instanceId == tInstanceId) && canSeeTarget(x, y, z, tx, ty, tz, instanceId); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instanceId + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, int instanceId) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instanceId)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceId() != target.getInstanceId()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceId())) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceId())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceId())) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceId())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = Util.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instanceId - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public Location getValidLocation(Location origin, Location destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), 0); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instanceId the instance id + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, int instanceId) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instanceId)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instanceId)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instanceId)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instanceId the instance id + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int instanceId) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instanceId)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instanceId)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instanceId)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instanceId - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instanceId)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instanceId)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = Util.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instanceId - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code Location} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public boolean canMoveToTarget(Location from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instanceId)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, 0); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code Location} to start checking from + * @param to the {@code Location} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(Location from, Location to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..31435828a8 --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,99 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..0f7ce8d0f9 --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,402 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instanceId)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..c5cb28edaf --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,444 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(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 = GeoEngine.getInstance().getValidLocation(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 searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java index 4eff2ac8bd..c927c95af4 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java @@ -47,8 +47,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { BuilderUtil.sendSysMessage(activeChar, "WorldX: " + worldX + ", WorldY: " + worldY + ", WorldZ: " + worldZ + ", GeoX: " + geoX + ", GeoY: " + geoY + ", GeoZ: " + GeoEngine.getInstance().getHeight(worldX, worldY, worldZ)); @@ -63,8 +63,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { BuilderUtil.sendSysMessage(activeChar, "WorldX: " + worldX + ", WorldY: " + worldY + ", WorldZ: " + worldZ + ", GeoX: " + geoX + ", GeoY: " + geoY + ", GeoZ: " + GeoEngine.getInstance().getHeight(worldX, worldY, worldZ)); diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminServerInfo.java index 1c5887ce1a..4c0093e008 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/handler/skillhandlers/Fishing.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/handler/skillhandlers/Fishing.java index 633c3a6e9e..6400c6e9d7 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/handler/skillhandlers/Fishing.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/handler/skillhandlers/Fishing.java @@ -133,11 +133,11 @@ public class Fishing implements ISkillHandler if ((water != null)) { final Location waterLocation = new Location(x, y, water.getWaterZ() - 50); - if ((aimingTo != null) && GeoEngine.getInstance().canSeeLocation(player, waterLocation)) + if ((aimingTo != null) && GeoEngine.getInstance().canSeeTarget(player, waterLocation)) { z = water.getWaterZ() + 10; } - else if ((aimingTo != null) && GeoEngine.getInstance().canSeeLocation(player, waterLocation)) + else if ((aimingTo != null) && GeoEngine.getInstance().canSeeTarget(player, waterLocation)) { z = aimingTo.getWaterZ() + 10; } diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/actor/Creature.java index 4a03e8f720..24e4ad2aff 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -42,6 +42,8 @@ import org.l2jmobius.gameserver.data.sql.NpcTable; import org.l2jmobius.gameserver.data.xml.MapRegionData; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.ISkillHandler; import org.l2jmobius.gameserver.handler.SkillHandler; import org.l2jmobius.gameserver.handler.itemhandlers.Potions; @@ -575,7 +577,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder // Adjust position a bit. int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); if (Config.RESPAWN_RANDOM_ENABLED && allowRandomOffset) { x += Rnd.get(-Config.RESPAWN_RANDOM_MAX_OFFSET, Config.RESPAWN_RANDOM_MAX_OFFSET); @@ -4191,7 +4193,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder public int _heading; public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -5330,7 +5332,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder } // Movement checks. - if (Config.PATHFINDING) + if (Config.PATHFINDING > 0) { final double originalDistance = distance; final int originalX = x; @@ -5394,7 +5396,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder if (((originalDistance - distance) > 30) && !_isAfraid && !isInBoat) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -5438,7 +5440,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder } // If no distance to go through, the movement is cancelled - if ((distance < 1) && (Config.PATHFINDING || isPlayable() || _isAfraid || (this instanceof RiftInvader))) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable() || _isAfraid || (this instanceof RiftInvader))) { if (isSummon()) { @@ -6897,7 +6899,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder } // Check if the target is behind a wall - if ((skill.getSkillRadius() > 0) && skill.isOffensive() && Config.PATHFINDING && !GeoEngine.getInstance().canSeeTarget(this, target)) + if ((skill.getSkillRadius() > 0) && skill.isOffensive() && (Config.PATHFINDING > 0) && !GeoEngine.getInstance().canSeeTarget(this, target)) { skipped++; continue; diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/actor/Player.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/actor/Player.java index dd327467dc..02b71249b3 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/actor/Player.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/actor/Player.java @@ -4330,7 +4330,7 @@ public class Player extends Playable } else if (isAutoAttackable(player)) { - if (Config.PATHFINDING) + if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(player, this)) { @@ -4344,7 +4344,7 @@ public class Player extends Playable player.onActionRequest(); } } - else if (Config.PATHFINDING) + else if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(player, this)) { @@ -4423,7 +4423,7 @@ public class Player extends Playable } else if (isAutoAttackable(player)) { - if (Config.PATHFINDING) + if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(player, this)) { @@ -4461,7 +4461,7 @@ public class Player extends Playable player.sendPacket(ActionFailed.STATIC_PACKET); } } - else if (Config.PATHFINDING) + else if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(player, this)) { diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/actor/Summon.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/actor/Summon.java index 98e157d8e6..daf80e33f7 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/actor/Summon.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/actor/Summon.java @@ -201,7 +201,7 @@ public abstract class Summon extends Playable { if (isAutoAttackable(player)) { - if (Config.PATHFINDING) + if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(player, this)) { @@ -220,7 +220,7 @@ public abstract class Summon extends Playable // This Action Failed packet avoids player getting stuck when clicking three or more times player.sendPacket(ActionFailed.STATIC_PACKET); - if (Config.PATHFINDING) + if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(player, this)) { diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/item/instance/Item.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/item/instance/Item.java index e2e1d30bbd..e7d571e075 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/item/instance/Item.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/item/instance/Item.java @@ -1031,7 +1031,7 @@ public class Item extends WorldObject int x = xValue; int y = yValue; int z = zValue; - if (Config.PATHFINDING && (dropper != null)) + if ((Config.PATHFINDING > 0) && (dropper != null)) { final Location dropDest = GeoEngine.getInstance().getValidLocation(dropper.getX(), dropper.getY(), dropper.getZ(), x, y, z, dropper.getInstanceId()); if ((dropDest != null) && (dropDest.getX() != 0) && (dropDest.getY() != 0)) diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/skill/Formulas.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/skill/Formulas.java index c43f2c159c..18dd63cfa6 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/skill/Formulas.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/skill/Formulas.java @@ -2938,7 +2938,7 @@ public class Formulas */ public static double calcFallDam(Creature creature, int fallHeight) { - if (!Config.FALL_DAMAGE || (fallHeight < 0)) + if (!Config.ENABLE_FALLING_DAMAGE || (fallHeight < 0)) { return 0; } diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/spawn/Spawn.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/spawn/Spawn.java index 02e6d7dcbc..2c9af0d114 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/spawn/Spawn.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/model/spawn/Spawn.java @@ -454,7 +454,7 @@ public class Spawn final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, getInstanceId()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, getInstanceId())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/util/GeoUtils.java index 12d75dd70d..d0d1e96c78 100644 --- a/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_C4_ScionsOfDestiny/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_C6_Interlude/dist/game/config/General.ini b/L2J_Mobius_C6_Interlude/dist/game/config/General.ini index 5e8f735b12..39d3fc2a2c 100644 --- a/L2J_Mobius_C6_Interlude/dist/game/config/General.ini +++ b/L2J_Mobius_C6_Interlude/dist/game/config/General.ini @@ -496,6 +496,7 @@ AltVillagesRepQuestReward = False # --------------------------------------------------------------------------- AllowLottery = True AllowRace = True +AllowWater = True AllowRentPet = False AllowFishing = True AllowBoat = True @@ -504,6 +505,15 @@ AllowManor = True AllowNpcWalkers = True +# --------------------------------------------------------------------------- +# Falling Damage +# --------------------------------------------------------------------------- + +# Allow characters to receive damage from falling. +# Default: True +EnableFallingDamage = True + + # --------------------------------------------------------------------------- # Server Optimization # --------------------------------------------------------------------------- diff --git a/L2J_Mobius_C6_Interlude/dist/game/config/GeoEngine.ini b/L2J_Mobius_C6_Interlude/dist/game/config/GeoEngine.ini index 5889c6caf0..dff362b8ff 100644 --- a/L2J_Mobius_C6_Interlude/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_C6_Interlude/dist/game/config/GeoEngine.ini @@ -2,51 +2,50 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False # ================================================================= # Other diff --git a/L2J_Mobius_C6_Interlude/dist/game/data/geodata/Readme.txt b/L2J_Mobius_C6_Interlude/dist/game/data/geodata/Readme.txt index 37ac3f8134..2919d7782d 100644 --- a/L2J_Mobius_C6_Interlude/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_C6_Interlude/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/main/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_C6_Interlude/dist/game/data/scripts/ai/bosses/Antharas.java b/L2J_Mobius_C6_Interlude/dist/game/data/scripts/ai/bosses/Antharas.java index 45dbb4314b..315d71ad2f 100644 --- a/L2J_Mobius_C6_Interlude/dist/game/data/scripts/ai/bosses/Antharas.java +++ b/L2J_Mobius_C6_Interlude/dist/game/data/scripts/ai/bosses/Antharas.java @@ -581,7 +581,7 @@ public class Antharas extends Quest final int ry = getRandom(112400, 116000); final int rdt = ((_antharas.getX() - rx) * (_antharas.getX() - rx)) + ((_antharas.getY() - ry) * (_antharas.getY() - ry)); final Location randomLocation = new Location(rx, ry, -7704); - if (GeoEngine.getInstance().canSeeLocation(_antharas, randomLocation) && (rdt < dt)) + if (GeoEngine.getInstance().canSeeTarget(_antharas, randomLocation) && (rdt < dt)) { x = rx; y = ry; diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/Config.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/Config.java index 5e40299f8c..696d1d1a66 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/Config.java @@ -54,7 +54,6 @@ import org.l2jmobius.commons.util.ClassMasterSettings; import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; import org.l2jmobius.gameserver.model.olympiad.OlympiadPeriod; @@ -181,10 +180,12 @@ public class Config public static boolean ALT_VILLAGES_REPEATABLE_QUEST_REWARD; public static boolean ALLOW_LOTTERY; public static boolean ALLOW_RACE; + public static boolean ALLOW_WATER; public static boolean ALLOW_RENTPET; public static boolean ALLOW_BOAT; public static boolean ALLOW_CURSED_WEAPONS; public static boolean ALLOW_NPC_WALKERS; + public static boolean ENABLE_FALLING_DAMAGE; public static int MIN_NPC_ANIMATION; public static int MAX_NPC_ANIMATION; public static int MIN_MONSTER_ANIMATION; @@ -907,20 +908,16 @@ public class Config public static boolean ALT_RAIDS_STATS_BONUS; public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; - public static boolean FALL_DAMAGE; - public static boolean ALLOW_WATER; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; public static int RBLOCKRAGE; public static boolean PLAYERS_CAN_HEAL_RB; @@ -1509,12 +1506,14 @@ public class Config ALT_VILLAGES_REPEATABLE_QUEST_REWARD = generalConfig.getBoolean("AltVillagesRepQuestReward", false); ALLOW_LOTTERY = generalConfig.getBoolean("AllowLottery", false); ALLOW_RACE = generalConfig.getBoolean("AllowRace", false); + ALLOW_WATER = generalConfig.getBoolean("AllowWater", false); ALLOW_RENTPET = generalConfig.getBoolean("AllowRentPet", false); ALLOW_DISCARDITEM = generalConfig.getBoolean("AllowDiscardItem", true); ALLOWFISHING = generalConfig.getBoolean("AllowFishing", false); ALLOW_MANOR = generalConfig.getBoolean("AllowManor", false); ALLOW_BOAT = generalConfig.getBoolean("AllowBoat", false); ALLOW_NPC_WALKERS = generalConfig.getBoolean("AllowNpcWalkers", true); + ENABLE_FALLING_DAMAGE = generalConfig.getBoolean("EnableFallingDamage", true); ALLOW_CURSED_WEAPONS = generalConfig.getBoolean("AllowCursedWeapons", false); DEFAULT_GLOBAL_CHAT = generalConfig.getString("GlobalChat", "ON"); DEFAULT_TRADE_CHAT = generalConfig.getString("TradeChat", "ON"); @@ -2463,22 +2462,18 @@ public class Config public static void loadgeodataConfig() { - final PropertiesParser geoengineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoengineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoengineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoengineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoengineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoengineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoengineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoengineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoengineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoengineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoengineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoengineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoengineConfig.getInt("MaxObstacleHeight", 32); - FALL_DAMAGE = geoengineConfig.getBoolean("FallDamage", false); - ALLOW_WATER = geoengineConfig.getBoolean("AllowWater", false); + final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); } public static void loadBossConfig() diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/ai/AttackableAI.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/ai/AttackableAI.java index b92c551d05..60481ef79d 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/ai/AttackableAI.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/ai/AttackableAI.java @@ -792,7 +792,7 @@ public class AttackableAI extends CreatureAI if (!_actor.isInsideRadius2D(newX, newY, originalAttackTarget.getZ(), collision)) { final int newZ = _actor.getZ() + 30; - if (!Config.PATHFINDING || GeoEngine.getInstance().canMoveToTarget(_actor.getX(), _actor.getY(), _actor.getZ(), newX, newY, newZ, _actor.getInstanceId())) + if ((Config.PATHFINDING <= 0) || GeoEngine.getInstance().canMoveToTarget(_actor.getX(), _actor.getY(), _actor.getZ(), newX, newY, newZ, _actor.getInstanceId())) { moveTo(newX, newY, newZ); } diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/ai/SiegeGuardAI.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/ai/SiegeGuardAI.java index 216ce4532f..13fb1ad0e3 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/ai/SiegeGuardAI.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/ai/SiegeGuardAI.java @@ -605,7 +605,7 @@ public class SiegeGuardAI extends CreatureAI implements Runnable // Check if the WorldObject is inside the Faction Range of the actor if ((npc.getAI() != null) && ((npc.getAI().getIntention() == AI_INTENTION_IDLE) || (npc.getAI().getIntention() == AI_INTENTION_ACTIVE)) && actor.isInsideRadius2D(npc, npc.getFactionRange()) && target.isInsideRadius2D(npc, npc.getFactionRange())) { - if (Config.PATHFINDING) + if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(npc, target)) { diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index cdcb4ec7e2..a96dbcf0e7 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1062 +16,580 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; -import org.l2jmobius.gameserver.util.Util; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceId(), target.getX(), target.getY(), target.getZ(), target.getInstanceId())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, Location worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceId()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instanceId + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstanceId the target's instance id + * @return + */ + public boolean canSeeTarget(int x, int y, int z, int instanceId, int tx, int ty, int tz, int tInstanceId) + { + return (instanceId == tInstanceId) && canSeeTarget(x, y, z, tx, ty, tz, instanceId); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instanceId + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, int instanceId) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instanceId)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceId() != target.getInstanceId()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceId())) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceId())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceId())) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceId())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = Util.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instanceId - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public Location getValidLocation(Location origin, Location destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), 0); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instanceId the instance id + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, int instanceId) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instanceId)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instanceId)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instanceId)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instanceId the instance id + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int instanceId) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instanceId)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instanceId)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instanceId)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instanceId - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instanceId)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instanceId)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = Util.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instanceId - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code Location} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public boolean canMoveToTarget(Location from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instanceId)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, 0); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code Location} to start checking from + * @param to the {@code Location} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(Location from, Location to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..31435828a8 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,99 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..0f7ce8d0f9 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,402 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instanceId)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..c5cb28edaf --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,444 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(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 = GeoEngine.getInstance().getValidLocation(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 searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java index 4eff2ac8bd..c927c95af4 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java @@ -47,8 +47,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { BuilderUtil.sendSysMessage(activeChar, "WorldX: " + worldX + ", WorldY: " + worldY + ", WorldZ: " + worldZ + ", GeoX: " + geoX + ", GeoY: " + geoY + ", GeoZ: " + GeoEngine.getInstance().getHeight(worldX, worldY, worldZ)); @@ -63,8 +63,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { BuilderUtil.sendSysMessage(activeChar, "WorldX: " + worldX + ", WorldY: " + worldY + ", WorldZ: " + worldZ + ", GeoX: " + geoX + ", GeoY: " + geoY + ", GeoZ: " + GeoEngine.getInstance().getHeight(worldX, worldY, worldZ)); diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminServerInfo.java index 1c5887ce1a..4c0093e008 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/skillhandlers/Fishing.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/skillhandlers/Fishing.java index 633c3a6e9e..6400c6e9d7 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/skillhandlers/Fishing.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/skillhandlers/Fishing.java @@ -133,11 +133,11 @@ public class Fishing implements ISkillHandler if ((water != null)) { final Location waterLocation = new Location(x, y, water.getWaterZ() - 50); - if ((aimingTo != null) && GeoEngine.getInstance().canSeeLocation(player, waterLocation)) + if ((aimingTo != null) && GeoEngine.getInstance().canSeeTarget(player, waterLocation)) { z = water.getWaterZ() + 10; } - else if ((aimingTo != null) && GeoEngine.getInstance().canSeeLocation(player, waterLocation)) + else if ((aimingTo != null) && GeoEngine.getInstance().canSeeTarget(player, waterLocation)) { z = aimingTo.getWaterZ() + 10; } diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/Creature.java index 5b07828f54..26fdcc4bdb 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -42,6 +42,8 @@ import org.l2jmobius.gameserver.data.sql.NpcTable; import org.l2jmobius.gameserver.data.xml.MapRegionData; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.ISkillHandler; import org.l2jmobius.gameserver.handler.SkillHandler; import org.l2jmobius.gameserver.handler.itemhandlers.Potions; @@ -577,7 +579,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder // Adjust position a bit. int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); if (Config.RESPAWN_RANDOM_ENABLED && allowRandomOffset) { x += Rnd.get(-Config.RESPAWN_RANDOM_MAX_OFFSET, Config.RESPAWN_RANDOM_MAX_OFFSET); @@ -4237,7 +4239,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder public int _heading; public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -5376,7 +5378,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder } // Movement checks. - if (Config.PATHFINDING) + if (Config.PATHFINDING > 0) { final double originalDistance = distance; final int originalX = x; @@ -5440,7 +5442,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder if (((originalDistance - distance) > 30) && !_isAfraid && !isInBoat) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -5484,7 +5486,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder } // If no distance to go through, the movement is cancelled - if ((distance < 1) && (Config.PATHFINDING || isPlayable() || _isAfraid || (this instanceof RiftInvader))) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable() || _isAfraid || (this instanceof RiftInvader))) { if (isSummon()) { @@ -6944,7 +6946,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder } // Check if the target is behind a wall - if ((skill.getSkillRadius() > 0) && skill.isOffensive() && Config.PATHFINDING && !GeoEngine.getInstance().canSeeTarget(this, target)) + if ((skill.getSkillRadius() > 0) && skill.isOffensive() && (Config.PATHFINDING > 0) && !GeoEngine.getInstance().canSeeTarget(this, target)) { skipped++; continue; diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/Player.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/Player.java index c2bc026f1e..45cf688789 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/Player.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/Player.java @@ -4418,7 +4418,7 @@ public class Player extends Playable { player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (Config.PATHFINDING) + else if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(player, this)) { @@ -4432,7 +4432,7 @@ public class Player extends Playable player.onActionRequest(); } } - else if (Config.PATHFINDING) + else if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(player, this)) { @@ -4517,7 +4517,7 @@ public class Player extends Playable { player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (Config.PATHFINDING) + else if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(player, this)) { @@ -4555,7 +4555,7 @@ public class Player extends Playable player.sendPacket(ActionFailed.STATIC_PACKET); } } - else if (Config.PATHFINDING) + else if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(player, this)) { diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/Summon.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/Summon.java index 98e157d8e6..daf80e33f7 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/Summon.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/Summon.java @@ -201,7 +201,7 @@ public abstract class Summon extends Playable { if (isAutoAttackable(player)) { - if (Config.PATHFINDING) + if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(player, this)) { @@ -220,7 +220,7 @@ public abstract class Summon extends Playable // This Action Failed packet avoids player getting stuck when clicking three or more times player.sendPacket(ActionFailed.STATIC_PACKET); - if (Config.PATHFINDING) + if (Config.PATHFINDING > 0) { if (GeoEngine.getInstance().canSeeTarget(player, this)) { diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/item/instance/Item.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/item/instance/Item.java index fc3dabefdb..cd0b90fa61 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/item/instance/Item.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/item/instance/Item.java @@ -1092,7 +1092,7 @@ public class Item extends WorldObject int x = xValue; int y = yValue; int z = zValue; - if (Config.PATHFINDING && (dropper != null)) + if ((Config.PATHFINDING > 0) && (dropper != null)) { final Location dropDest = GeoEngine.getInstance().getValidLocation(dropper.getX(), dropper.getY(), dropper.getZ(), x, y, z, dropper.getInstanceId()); if ((dropDest != null) && (dropDest.getX() != 0) && (dropDest.getY() != 0)) diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/skill/Formulas.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/skill/Formulas.java index c43f2c159c..18dd63cfa6 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/skill/Formulas.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/skill/Formulas.java @@ -2938,7 +2938,7 @@ public class Formulas */ public static double calcFallDam(Creature creature, int fallHeight) { - if (!Config.FALL_DAMAGE || (fallHeight < 0)) + if (!Config.ENABLE_FALLING_DAMAGE || (fallHeight < 0)) { return 0; } diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/spawn/Spawn.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/spawn/Spawn.java index 02e6d7dcbc..2c9af0d114 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/spawn/Spawn.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/spawn/Spawn.java @@ -454,7 +454,7 @@ public class Spawn final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, getInstanceId()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, getInstanceId())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/util/GeoUtils.java index 12d75dd70d..d0d1e96c78 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/config/General.ini b/L2J_Mobius_CT_0_Interlude/dist/game/config/General.ini index 857a0b1e7d..c8d65965d5 100644 --- a/L2J_Mobius_CT_0_Interlude/dist/game/config/General.ini +++ b/L2J_Mobius_CT_0_Interlude/dist/game/config/General.ini @@ -283,7 +283,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/config/GeoEngine.ini b/L2J_Mobius_CT_0_Interlude/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_CT_0_Interlude/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_CT_0_Interlude/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/geodata/Readme.txt b/L2J_Mobius_CT_0_Interlude/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_CT_0_Interlude/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 99e64fd932..1cf5c766d5 100644 --- a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceId()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceId(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/effecthandlers/Fishing.java b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/effecthandlers/Fishing.java index a62af6bef4..3bbd8aa556 100644 --- a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/effecthandlers/Fishing.java +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/effecthandlers/Fishing.java @@ -236,7 +236,7 @@ public class Fishing extends AbstractEffect // always use water zone, fishing zone high z is high in the air... final int baitZ = waterZone.getWaterZ(); - if (!GeoEngine.getInstance().canSeeLocation(player, new Location(baitX, baitY, baitZ))) + if (!GeoEngine.getInstance().canSeeTarget(player, new Location(baitX, baitY, baitZ))) { return Integer.MIN_VALUE; } diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/Config.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/Config.java index e263a9cca1..a3b8101b71 100644 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/Config.java @@ -60,7 +60,6 @@ import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -1020,18 +1019,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; // -------------------------------------------------- // Custom Settings @@ -2466,19 +2463,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/CreatureAI.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/CreatureAI.java index ad2eb7ecce..189bbe1e99 100644 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/CreatureAI.java +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/CreatureAI.java @@ -930,7 +930,7 @@ public class CreatureAI extends AbstractAI } // If pathfinding enabled the creature will go to the destination or it will go to the nearest obstacle. - setIntention(AI_INTENTION_MOVE_TO, Config.PATHFINDING ? GeoEngine.getInstance().getValidLocation(_actor.getX(), _actor.getY(), _actor.getZ(), posX, posY, posZ, _actor.getInstanceId()) : new Location(posX, posY, posZ)); + setIntention(AI_INTENTION_MOVE_TO, Config.PATHFINDING > 0 ? GeoEngine.getInstance().getValidLocation(_actor.getX(), _actor.getY(), _actor.getZ(), posX, posY, posZ, _actor.getInstanceId()) : new Location(posX, posY, posZ)); } protected boolean maybeMoveToPosition(ILocational worldPosition, int offset) diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/SummonAI.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/SummonAI.java index ce211f8cae..0df95b588b 100644 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/SummonAI.java +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/SummonAI.java @@ -26,6 +26,7 @@ import org.l2jmobius.Config; import org.l2jmobius.commons.threads.ThreadPool; import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.model.WorldObject; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; @@ -50,7 +51,7 @@ public class SummonAI extends PlayableAI implements Runnable @Override protected void onIntentionAttack(Creature target) { - if (Config.PATHFINDING && (GeoEngine.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), target.getX(), target.getY(), target.getZ(), _actor.getInstanceId()) == null)) + if ((Config.PATHFINDING > 0) && (PathFinding.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), target.getX(), target.getY(), target.getZ(), _actor.getInstanceId(), false) == null)) { return; } diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index c8d45f8574..ccff4e6ee9 100644 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1062 +16,581 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceId(), target.getX(), target.getY(), target.getZ(), target.getInstanceId())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceId()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instanceId + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstanceId the target's instance id + * @return + */ + public boolean canSeeTarget(int x, int y, int z, int instanceId, int tx, int ty, int tz, int tInstanceId) + { + return (instanceId == tInstanceId) && canSeeTarget(x, y, z, tx, ty, tz, instanceId); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instanceId + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, int instanceId) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instanceId)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceId() != target.getInstanceId()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceId(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceId())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceId(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceId())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instanceId - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), 0); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instanceId the instance id + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, int instanceId) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instanceId, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instanceId, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instanceId)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instanceId the instance id + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int instanceId) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instanceId, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instanceId)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instanceId)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instanceId - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instanceId, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instanceId)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instanceId - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instanceId)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, 0); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..31435828a8 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,99 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..0f7ce8d0f9 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,402 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instanceId)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..c5cb28edaf --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,444 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(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 = GeoEngine.getInstance().getValidLocation(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 searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/Spawn.java index 211350c629..a49936809d 100644 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/Spawn.java @@ -394,7 +394,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, getInstanceId()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, getInstanceId())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/Creature.java index 27dfaad939..72cae96547 100644 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -58,6 +58,8 @@ import org.l2jmobius.gameserver.enums.SkillFinishType; import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.InstanceManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; @@ -718,7 +720,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; // Prepare creature for teleport. @@ -3325,7 +3327,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -4241,7 +4243,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof QuestGuard)) + if ((Config.PATHFINDING > 0) && !(this instanceof QuestGuard)) { final double originalDistance = distance; final int originalX = x; @@ -4285,7 +4287,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isAfraid() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -4329,7 +4331,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/Player.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/Player.java index aa016aa605..eb812293f6 100644 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/Player.java +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/Player.java @@ -8551,7 +8551,7 @@ public class Player extends Playable { if (sklTargetType == TargetType.GROUND) { - if (!GeoEngine.getInstance().canSeeLocation(this, _currentSkillWorldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(this, _currentSkillWorldPosition)) { sendPacket(SystemMessageId.CANNOT_SEE_TARGET); sendPacket(ActionFailed.STATIC_PACKET); diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/Summon.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/Summon.java index 901963c1a2..642061c841 100644 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/Summon.java +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/Summon.java @@ -29,6 +29,7 @@ import org.l2jmobius.gameserver.enums.Race; import org.l2jmobius.gameserver.enums.ShotType; import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IItemHandler; import org.l2jmobius.gameserver.handler.ItemHandler; import org.l2jmobius.gameserver.instancemanager.ZoneManager; @@ -640,7 +641,7 @@ public abstract class Summon extends Playable return false; } - if ((this != target) && skill.isPhysical() && Config.PATHFINDING && (GeoEngine.getInstance().findPath(getX(), getY(), getZ(), target.getX(), target.getY(), target.getZ(), getInstanceId()) == null)) + if ((this != target) && skill.isPhysical() && (Config.PATHFINDING > 0) && (PathFinding.getInstance().findPath(getX(), getY(), getZ(), target.getX(), target.getY(), target.getZ(), getInstanceId(), false) == null)) { sendPacket(SystemMessageId.CANNOT_SEE_TARGET); return false; diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/util/GeoUtils.java index 12d75dd70d..d0d1e96c78 100644 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/General.ini b/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/General.ini index 4f8e14504f..4055971deb 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/General.ini +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/General.ini @@ -283,7 +283,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/GeoEngine.ini b/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/geodata/Readme.txt b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 99e64fd932..1cf5c766d5 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceId()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceId(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/effecthandlers/Fishing.java b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/effecthandlers/Fishing.java index a62af6bef4..3bbd8aa556 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/effecthandlers/Fishing.java +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/effecthandlers/Fishing.java @@ -236,7 +236,7 @@ public class Fishing extends AbstractEffect // always use water zone, fishing zone high z is high in the air... final int baitZ = waterZone.getWaterZ(); - if (!GeoEngine.getInstance().canSeeLocation(player, new Location(baitX, baitY, baitZ))) + if (!GeoEngine.getInstance().canSeeTarget(player, new Location(baitX, baitY, baitZ))) { return Integer.MIN_VALUE; } diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/Config.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/Config.java index 070244bc9a..1b971f0898 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/Config.java @@ -60,7 +60,6 @@ import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -1073,18 +1072,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; // -------------------------------------------------- // Custom Settings @@ -2579,19 +2576,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/ai/CreatureAI.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/ai/CreatureAI.java index 73a0418cd1..faa974196a 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/ai/CreatureAI.java +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/ai/CreatureAI.java @@ -932,7 +932,7 @@ public class CreatureAI extends AbstractAI } // If pathfinding enabled the creature will go to the destination or it will go to the nearest obstacle. - setIntention(AI_INTENTION_MOVE_TO, Config.PATHFINDING ? GeoEngine.getInstance().getValidLocation(_actor.getX(), _actor.getY(), _actor.getZ(), posX, posY, posZ, _actor.getInstanceId()) : new Location(posX, posY, posZ)); + setIntention(AI_INTENTION_MOVE_TO, Config.PATHFINDING > 0 ? GeoEngine.getInstance().getValidLocation(_actor.getX(), _actor.getY(), _actor.getZ(), posX, posY, posZ, _actor.getInstanceId()) : new Location(posX, posY, posZ)); } protected boolean maybeMoveToPosition(ILocational worldPosition, int offset) diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/ai/SummonAI.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/ai/SummonAI.java index ce211f8cae..0df95b588b 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/ai/SummonAI.java +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/ai/SummonAI.java @@ -26,6 +26,7 @@ import org.l2jmobius.Config; import org.l2jmobius.commons.threads.ThreadPool; import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.model.WorldObject; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; @@ -50,7 +51,7 @@ public class SummonAI extends PlayableAI implements Runnable @Override protected void onIntentionAttack(Creature target) { - if (Config.PATHFINDING && (GeoEngine.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), target.getX(), target.getY(), target.getZ(), _actor.getInstanceId()) == null)) + if ((Config.PATHFINDING > 0) && (PathFinding.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), target.getX(), target.getY(), target.getZ(), _actor.getInstanceId(), false) == null)) { return; } diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index c8d45f8574..ccff4e6ee9 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1062 +16,581 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceId(), target.getX(), target.getY(), target.getZ(), target.getInstanceId())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceId()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instanceId + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstanceId the target's instance id + * @return + */ + public boolean canSeeTarget(int x, int y, int z, int instanceId, int tx, int ty, int tz, int tInstanceId) + { + return (instanceId == tInstanceId) && canSeeTarget(x, y, z, tx, ty, tz, instanceId); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instanceId + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, int instanceId) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instanceId)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceId() != target.getInstanceId()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceId(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceId())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceId(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceId())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instanceId - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), 0); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instanceId the instance id + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, int instanceId) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instanceId, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instanceId, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instanceId)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instanceId the instance id + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int instanceId) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instanceId, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instanceId)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instanceId)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instanceId - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instanceId, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instanceId)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instanceId - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instanceId)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, 0); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..31435828a8 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,99 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..0f7ce8d0f9 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,402 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instanceId)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..c5cb28edaf --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,444 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(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 = GeoEngine.getInstance().getValidLocation(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 searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/Spawn.java index bae18cb1de..a3e996371f 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/Spawn.java @@ -394,7 +394,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, getInstanceId()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, getInstanceId())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/Creature.java index e601437d57..ee53e365fa 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -60,6 +60,8 @@ import org.l2jmobius.gameserver.enums.SkillFinishType; import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.InstanceManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; @@ -748,7 +750,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; // Prepare creature for teleport. @@ -3510,7 +3512,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -4426,7 +4428,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof QuestGuard)) + if ((Config.PATHFINDING > 0) && !(this instanceof QuestGuard)) { final double originalDistance = distance; final int originalX = x; @@ -4470,7 +4472,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isAfraid() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -4514,7 +4516,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/Player.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/Player.java index a15a42a4af..58b221abd0 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/Player.java +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/Player.java @@ -9020,7 +9020,7 @@ public class Player extends Playable { if (sklTargetType == TargetType.GROUND) { - if (!GeoEngine.getInstance().canSeeLocation(this, _currentSkillWorldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(this, _currentSkillWorldPosition)) { sendPacket(SystemMessageId.CANNOT_SEE_TARGET); sendPacket(ActionFailed.STATIC_PACKET); diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/Summon.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/Summon.java index 23b3ba0468..3546f60a58 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/Summon.java +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/Summon.java @@ -29,6 +29,7 @@ import org.l2jmobius.gameserver.enums.Race; import org.l2jmobius.gameserver.enums.ShotType; import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IItemHandler; import org.l2jmobius.gameserver.handler.ItemHandler; import org.l2jmobius.gameserver.instancemanager.TerritoryWarManager; @@ -644,7 +645,7 @@ public abstract class Summon extends Playable return false; } - if ((this != target) && skill.isPhysical() && Config.PATHFINDING && (GeoEngine.getInstance().findPath(getX(), getY(), getZ(), target.getX(), target.getY(), target.getZ(), getInstanceId()) == null)) + if ((this != target) && skill.isPhysical() && (Config.PATHFINDING > 0) && (PathFinding.getInstance().findPath(getX(), getY(), getZ(), target.getX(), target.getY(), target.getZ(), getInstanceId(), false) == null)) { sendPacket(SystemMessageId.CANNOT_SEE_TARGET); return false; diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/config/General.ini b/L2J_Mobius_CT_2.6_HighFive/dist/game/config/General.ini index 4f8e14504f..4055971deb 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/config/General.ini +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/config/General.ini @@ -283,7 +283,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/config/GeoEngine.ini b/L2J_Mobius_CT_2.6_HighFive/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/geodata/Readme.txt b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 99e64fd932..1cf5c766d5 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceId()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceId(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/effecthandlers/Fishing.java b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/effecthandlers/Fishing.java index a62af6bef4..3bbd8aa556 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/effecthandlers/Fishing.java +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/effecthandlers/Fishing.java @@ -236,7 +236,7 @@ public class Fishing extends AbstractEffect // always use water zone, fishing zone high z is high in the air... final int baitZ = waterZone.getWaterZ(); - if (!GeoEngine.getInstance().canSeeLocation(player, new Location(baitX, baitY, baitZ))) + if (!GeoEngine.getInstance().canSeeTarget(player, new Location(baitX, baitY, baitZ))) { return Integer.MIN_VALUE; } diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/Config.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/Config.java index c87504b7ee..b321006a02 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/Config.java @@ -60,7 +60,6 @@ import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -1073,18 +1072,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; // -------------------------------------------------- // Custom Settings @@ -2586,19 +2583,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/ai/CreatureAI.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/ai/CreatureAI.java index 73a0418cd1..faa974196a 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/ai/CreatureAI.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/ai/CreatureAI.java @@ -932,7 +932,7 @@ public class CreatureAI extends AbstractAI } // If pathfinding enabled the creature will go to the destination or it will go to the nearest obstacle. - setIntention(AI_INTENTION_MOVE_TO, Config.PATHFINDING ? GeoEngine.getInstance().getValidLocation(_actor.getX(), _actor.getY(), _actor.getZ(), posX, posY, posZ, _actor.getInstanceId()) : new Location(posX, posY, posZ)); + setIntention(AI_INTENTION_MOVE_TO, Config.PATHFINDING > 0 ? GeoEngine.getInstance().getValidLocation(_actor.getX(), _actor.getY(), _actor.getZ(), posX, posY, posZ, _actor.getInstanceId()) : new Location(posX, posY, posZ)); } protected boolean maybeMoveToPosition(ILocational worldPosition, int offset) diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/ai/SummonAI.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/ai/SummonAI.java index ce211f8cae..0df95b588b 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/ai/SummonAI.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/ai/SummonAI.java @@ -26,6 +26,7 @@ import org.l2jmobius.Config; import org.l2jmobius.commons.threads.ThreadPool; import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.model.WorldObject; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; @@ -50,7 +51,7 @@ public class SummonAI extends PlayableAI implements Runnable @Override protected void onIntentionAttack(Creature target) { - if (Config.PATHFINDING && (GeoEngine.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), target.getX(), target.getY(), target.getZ(), _actor.getInstanceId()) == null)) + if ((Config.PATHFINDING > 0) && (PathFinding.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), target.getX(), target.getY(), target.getZ(), _actor.getInstanceId(), false) == null)) { return; } diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index c8d45f8574..ccff4e6ee9 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1062 +16,581 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceId(), target.getX(), target.getY(), target.getZ(), target.getInstanceId())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceId()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instanceId + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstanceId the target's instance id + * @return + */ + public boolean canSeeTarget(int x, int y, int z, int instanceId, int tx, int ty, int tz, int tInstanceId) + { + return (instanceId == tInstanceId) && canSeeTarget(x, y, z, tx, ty, tz, instanceId); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instanceId + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, int instanceId) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instanceId)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceId() != target.getInstanceId()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceId(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceId())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceId(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceId())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getTemplate().getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instanceId - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), 0); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instanceId the instance id + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, int instanceId) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instanceId, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instanceId, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instanceId)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instanceId the instance id + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int instanceId) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instanceId, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instanceId)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instanceId)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instanceId - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instanceId, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instanceId)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instanceId - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instanceId)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, 0); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..31435828a8 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,99 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..0f7ce8d0f9 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,402 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instanceId)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..c5cb28edaf --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,444 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(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 = GeoEngine.getInstance().getValidLocation(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 searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/Spawn.java index 211350c629..a49936809d 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/Spawn.java @@ -394,7 +394,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, getInstanceId()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, getInstanceId())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/Creature.java index b75c54bca1..15afe607be 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -60,6 +60,8 @@ import org.l2jmobius.gameserver.enums.SkillFinishType; import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.InstanceManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; @@ -749,7 +751,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; // Prepare creature for teleport. @@ -3512,7 +3514,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -4428,7 +4430,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof QuestGuard)) + if ((Config.PATHFINDING > 0) && !(this instanceof QuestGuard)) { final double originalDistance = distance; final int originalX = x; @@ -4472,7 +4474,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isAfraid() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -4516,7 +4518,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/Player.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/Player.java index 0aca4bccf5..493cfda35f 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/Player.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/Player.java @@ -8904,7 +8904,7 @@ public class Player extends Playable { if (sklTargetType == TargetType.GROUND) { - if (!GeoEngine.getInstance().canSeeLocation(this, _currentSkillWorldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(this, _currentSkillWorldPosition)) { sendPacket(SystemMessageId.CANNOT_SEE_TARGET); sendPacket(ActionFailed.STATIC_PACKET); diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/Summon.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/Summon.java index 7f92590582..5bb99abef9 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/Summon.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/Summon.java @@ -29,6 +29,7 @@ import org.l2jmobius.gameserver.enums.Race; import org.l2jmobius.gameserver.enums.ShotType; import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IItemHandler; import org.l2jmobius.gameserver.handler.ItemHandler; import org.l2jmobius.gameserver.instancemanager.TerritoryWarManager; @@ -644,7 +645,7 @@ public abstract class Summon extends Playable return false; } - if ((this != target) && skill.isPhysical() && Config.PATHFINDING && (GeoEngine.getInstance().findPath(getX(), getY(), getZ(), target.getX(), target.getY(), target.getZ(), getInstanceId()) == null)) + if ((this != target) && skill.isPhysical() && (Config.PATHFINDING > 0) && (PathFinding.getInstance().findPath(getX(), getY(), getZ(), target.getX(), target.getY(), target.getZ(), getInstanceId(), false) == null)) { sendPacket(SystemMessageId.CANNOT_SEE_TARGET); return false; diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/General.ini b/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/General.ini index 9de4791aa7..bb5e02c907 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/GeoEngine.ini b/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/geodata/Readme.txt b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/targethandlers/Ground.java index 0d22bf85ce..5ac9e1e0de 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/Config.java index 661df0f94c..d27b0db0c4 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -903,18 +902,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2409,19 +2406,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/actor/Creature.java index 667eb7361e..84bed767c3 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3465,7 +3467,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3509,7 +3511,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3553,7 +3555,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/config/General.ini b/L2J_Mobius_Classic_2.1_Zaken/dist/game/config/General.ini index 9de4791aa7..bb5e02c907 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/config/GeoEngine.ini b/L2J_Mobius_Classic_2.1_Zaken/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/geodata/Readme.txt b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/targethandlers/Ground.java index 9247390466..2d32a908c5 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/Config.java index e66c22bfde..f9bdf6230b 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -907,18 +906,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2415,19 +2412,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/model/actor/Creature.java index 667eb7361e..84bed767c3 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3465,7 +3467,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3509,7 +3511,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3553,7 +3555,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/config/General.ini b/L2J_Mobius_Classic_2.2_Antharas/dist/game/config/General.ini index 9de4791aa7..bb5e02c907 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/config/GeoEngine.ini b/L2J_Mobius_Classic_2.2_Antharas/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/geodata/Readme.txt b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/targethandlers/Ground.java index 9247390466..2d32a908c5 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/Config.java index e66c22bfde..f9bdf6230b 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -907,18 +906,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2415,19 +2412,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/model/actor/Creature.java index 212ba92fad..484c653f11 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3465,7 +3467,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3509,7 +3511,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3553,7 +3555,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/config/General.ini b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/config/General.ini index 9de4791aa7..bb5e02c907 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/config/GeoEngine.ini b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/geodata/Readme.txt b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/targethandlers/Ground.java index 9247390466..2d32a908c5 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/Config.java index e66c22bfde..f9bdf6230b 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -907,18 +906,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2415,19 +2412,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/model/actor/Creature.java index ff609ad715..aca53747f5 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -68,6 +68,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -822,7 +824,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2689,7 +2691,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3478,7 +3480,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3522,7 +3524,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3566,7 +3568,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/config/General.ini b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/config/General.ini index 9de4791aa7..bb5e02c907 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/config/GeoEngine.ini b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/geodata/Readme.txt b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/targethandlers/Ground.java index 9247390466..2d32a908c5 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/Config.java index a0577a1773..d699075807 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -912,18 +911,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2424,19 +2421,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/model/actor/Creature.java index ff609ad715..aca53747f5 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -68,6 +68,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -822,7 +824,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2689,7 +2691,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3478,7 +3480,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3522,7 +3524,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3566,7 +3568,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/General.ini b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/General.ini index 09454a473c..7fcc7b0fc3 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/GeoEngine.ini b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/geodata/Readme.txt b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/targethandlers/Ground.java index 9247390466..2d32a908c5 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/Config.java index e0ca4339dc..3e9e6fdace 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -918,18 +917,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2469,19 +2466,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/actor/Creature.java index 71f72cb364..a0cc05d0cb 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -68,6 +68,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -822,7 +824,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2689,7 +2691,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3477,7 +3479,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3521,7 +3523,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3565,7 +3567,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_Classic_Interlude/dist/game/config/General.ini b/L2J_Mobius_Classic_Interlude/dist/game/config/General.ini index ec443d27e8..1ac051ef1e 100644 --- a/L2J_Mobius_Classic_Interlude/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_Interlude/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_Classic_Interlude/dist/game/config/GeoEngine.ini b/L2J_Mobius_Classic_Interlude/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_Classic_Interlude/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_Classic_Interlude/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/geodata/Readme.txt b/L2J_Mobius_Classic_Interlude/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_Classic_Interlude/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/targethandlers/Ground.java index 9247390466..2d32a908c5 100644 --- a/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/Config.java index d6d65f5746..822cbc813d 100644 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -919,18 +918,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2429,19 +2426,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/actor/Creature.java index 667eb7361e..84bed767c3 100644 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2688,7 +2690,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3465,7 +3467,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3509,7 +3511,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3553,7 +3555,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/config/General.ini b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/config/General.ini index 4096e5f053..9b3390b2b6 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/config/General.ini +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/config/GeoEngine.ini b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/geodata/Readme.txt b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/targethandlers/Ground.java index 9247390466..2d32a908c5 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/Config.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/Config.java index 83072615fd..6d9b6a7f2e 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/Config.java @@ -61,7 +61,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -943,18 +942,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2520,19 +2517,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/model/actor/Creature.java index cfe8793810..a99ee46540 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -68,6 +68,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -822,7 +824,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2692,7 +2694,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3480,7 +3482,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3524,7 +3526,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3568,7 +3570,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/config/General.ini b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/config/General.ini index b55a61653a..b42a936a6a 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/config/General.ini +++ b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/config/GeoEngine.ini b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/geodata/Readme.txt b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/targethandlers/Ground.java index 9247390466..2d32a908c5 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/Config.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/Config.java index a941dc19aa..dc0a9ee4cb 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/Config.java @@ -62,7 +62,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -951,18 +950,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2621,19 +2618,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/model/actor/Creature.java index dd9abfd37a..38eb4fb7eb 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -68,6 +68,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -822,7 +824,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2699,7 +2701,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3487,7 +3489,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3531,7 +3533,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3575,7 +3577,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/config/General.ini b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/config/General.ini index b55a61653a..b42a936a6a 100644 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/config/General.ini +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/config/GeoEngine.ini b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/geodata/Readme.txt b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/targethandlers/Ground.java index 9247390466..2d32a908c5 100644 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/Config.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/Config.java index 32659ea9e9..a4cba7b8a6 100644 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/Config.java @@ -62,7 +62,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -958,18 +957,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2635,19 +2632,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/model/actor/Creature.java index ea85b94223..ad2e31f8f5 100644 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2698,7 +2700,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3486,7 +3488,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3530,7 +3532,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3574,7 +3576,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_Essence_6.1_BattleChronicle/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else { diff --git a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/config/General.ini b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/config/General.ini index b55a61653a..b42a936a6a 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/config/General.ini +++ b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/config/General.ini @@ -294,7 +294,6 @@ CorrectPrices = True # --------------------------------------------------------------------------- # Allow characters to receive damage from falling. -# CoordSynchronize = 2 is recommended. # Default: True EnableFallingDamage = True diff --git a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/config/GeoEngine.ini b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/config/GeoEngine.ini index e9c22abb03..d9c2feb06e 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/config/GeoEngine.ini +++ b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/config/GeoEngine.ini @@ -2,48 +2,47 @@ # Geodata # ================================================================= -# Specifies the path to geodata files. For example, when using geodata files located -# at different folder/harddrive ("C:/Program Files/Lineage II/system/geodata/"), default: ./data/geodata/ +# Pathfinding options: +# 0 = Disabled +# 1 = Enabled using path node files. +# 2 = Enabled using geodata cells at runtime. +# Default: 0 +PathFinding = 2 + +# Geodata file directory. GeoDataPath = ./data/geodata/ -# Specifies the geodata files type. Default: L2J -# L2J: Using L2J geodata files (filename e.g. 22_16.l2j) -# L2OFF: Using L2OFF geodata files (filename e.g. 22_16_conv.dat) -GeoDataType = L2J +# Pathnode file directory. +# Default: pathnode +PathnodePath = ./data/pathnode/ -# ================================================================= -# Pathfinding -# ================================================================= +# Pathfinding array buffers configuration. +PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 -# When line of movement check fails, the pathfinding algoritm is performed to look for -# an alternative path (e.g. walk around obstacle), default: True -PathFinding = True +# Weight for nodes without obstacles far from walls. +LowWeight = 0.5 -# Pathfinding array buffers configuration, default: 1200x10;2000x10;3000x5;5000x3;10000x3 -PathFindBuffers = 1200x10;2000x10;3000x5;5000x3;10000x3 +# Weight for nodes near walls. +MediumWeight = 2 -# Movement weight, when moving from one to another axially and diagonally, default: 10 and 14 -MoveWeight = 10 -MoveWeightDiag = 14 +# Weight for nodes with obstacles. +HighWeight = 3 -# When movement flags of target node is blocked to any direction, use this weight instead of MoveWeight or MoveWeightDiag. -# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 30 -ObstacleWeight = 30 +# Angle paths will be more "smart", but in cost of higher CPU utilization. +AdvancedDiagonalStrategy = True -# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 12 and 18 -# For proper function must be higher than MoveWeight. -HeuristicWeight = 12 -HeuristicWeightDiag = 18 +# Weight for diagonal movement. Used only with AdvancedDiagonalStrategy = True +# Default: LowWeight * sqrt(2) +DiagonalWeight = 0.707 -# Maximum number of generated nodes per one path-finding process, default 3500 -MaxIterations = 3500 +# Maximum number of LOS postfilter passes, 0 will disable postfilter. +# Default: 3 +MaxPostfilterPasses = 3 -# ================================================================= -# Line of Sight -# ================================================================= - -# Line of sight start at X percent of the character height, default: 75 -PartOfCharacterHeight = 75 - -# Maximum height of an obstacle, which can exceed the line of sight, default: 32 -MaxObstacleHeight = 32 +# Path debug function. +# Nodes known to pathfinder will be displayed as adena, constructed path as antidots. +# Number of the items show node cost * 10 +# Potions display path after first stage filter +# Red potions - actual waypoints. Green potions - nodes removed by LOS postfilter +# This function FOR DEBUG PURPOSES ONLY, never use it on the live server! +DebugPath = False diff --git a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/geodata/Readme.txt b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/geodata/Readme.txt index 2611882e56..217f76172b 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/geodata/Readme.txt +++ b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/geodata/Readme.txt @@ -25,6 +25,3 @@ b - Make it work To make geodata work: * unpack your geodata files into "/data/geodata" folder (or any other folder) -* open "/config/GeoEngine.ini" with your favorite text editor and then edit following configs: -- GeoDataPath = set path to your geodata, if elsewhere than "./data/geodata/" -- GeoDataType = set the geodata format, which you are using. diff --git a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java index 8fee14da00..c1b2cb296b 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +++ b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java @@ -55,8 +55,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { @@ -73,8 +73,8 @@ public class AdminGeodata implements IAdminCommandHandler final int worldX = activeChar.getX(); final int worldY = activeChar.getY(); final int worldZ = activeChar.getZ(); - final int geoX = GeoEngine.getGeoX(worldX); - final int geoY = GeoEngine.getGeoY(worldY); + final int geoX = GeoEngine.getInstance().getGeoX(worldX); + final int geoY = GeoEngine.getInstance().getGeoY(worldY); if (GeoEngine.getInstance().hasGeoPos(geoX, geoY)) { diff --git a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java index 36706fb860..8209d07690 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +++ b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java @@ -19,9 +19,9 @@ package handlers.admincommandhandlers; import java.util.List; import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.util.BuilderUtil; @@ -37,20 +37,21 @@ public class AdminPathNode implements IAdminCommandHandler { if (command.equals("admin_path_find")) { - if (!Config.PATHFINDING) + if (Config.PATHFINDING < 1) { BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); return true; } + if (activeChar.getTarget() != null) { - final List path = GeoEngine.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); + final List path = PathFinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld(), true); if (path == null) { BuilderUtil.sendSysMessage(activeChar, "No Route!"); return true; } - for (Location a : path) + for (AbstractNodeLoc a : path) { BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); } diff --git a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java index ddd3ef6a14..1e82359948 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java +++ b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/admincommandhandlers/AdminServerInfo.java @@ -72,7 +72,7 @@ public class AdminServerInfo implements IAdminCommandHandler html.replace("%slots%", getPlayersCount("ALL") + "/" + Config.MAXIMUM_ONLINE_USERS); html.replace("%gameTime%", GameTimeTaskManager.getInstance().getGameHour() + ":" + GameTimeTaskManager.getInstance().getGameMinute()); html.replace("%dayNight%", GameTimeTaskManager.getInstance().isNight() ? "Night" : "Day"); - html.replace("%geodata%", Config.PATHFINDING ? "Enabled" : "Disabled"); + html.replace("%geodata%", Config.PATHFINDING > 0 ? "Enabled" : "Disabled"); html.replace("%serverTime%", SDF.format(new Date(System.currentTimeMillis()))); html.replace("%serverUpTime%", getServerUpTime()); html.replace("%onlineAll%", getPlayersCount("ALL")); diff --git a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/targethandlers/Ground.java b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/targethandlers/Ground.java index 9247390466..2d32a908c5 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/targethandlers/Ground.java +++ b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/data/scripts/handlers/targethandlers/Ground.java @@ -52,7 +52,7 @@ public class Ground implements ITargetTypeHandler return null; } - if (!GeoEngine.getInstance().canSeeLocation(creature, worldPosition)) + if (!GeoEngine.getInstance().canSeeTarget(creature, worldPosition)) { if (sendMessage) { diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/Config.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/Config.java index 358c14633b..01c4e28640 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/Config.java @@ -62,7 +62,6 @@ import org.l2jmobius.commons.util.PropertiesParser; import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.enums.ChatType; import org.l2jmobius.gameserver.enums.ClassId; -import org.l2jmobius.gameserver.enums.GeoType; import org.l2jmobius.gameserver.enums.IllegalActionPunishmentType; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.holders.ItemHolder; @@ -958,18 +957,16 @@ public class Config // GeoEngine // -------------------------------------------------- public static Path GEODATA_PATH; - public static GeoType GEODATA_TYPE; - public static boolean PATHFINDING; + public static Path PATHNODE_PATH; + public static int PATHFINDING; public static String PATHFIND_BUFFERS; - public static int MOVE_WEIGHT; - public static int MOVE_WEIGHT_DIAG; - public static int OBSTACLE_WEIGHT; - public static int OBSTACLE_WEIGHT_DIAG; - public static int HEURISTIC_WEIGHT; - public static int HEURISTIC_WEIGHT_DIAG; - public static int MAX_ITERATIONS; - public static int PART_OF_CHARACTER_HEIGHT; - public static int MAX_OBSTACLE_HEIGHT; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + public static boolean DEBUG_PATH; /** Attribute System */ public static int S_WEAPON_STONE; @@ -2635,19 +2632,17 @@ public class Config // Load GeoEngine config file (if exists) final PropertiesParser geoEngineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); - GEODATA_PATH = Paths.get(geoEngineConfig.getString("GeoDataPath", "./data/geodata")); - GEODATA_TYPE = Enum.valueOf(GeoType.class, geoEngineConfig.getString("GeoDataType", "L2J")); - PATHFINDING = geoEngineConfig.getBoolean("PathFinding", true); - PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "1200x10;2000x10;3000x5;5000x3;10000x3"); - MOVE_WEIGHT = geoEngineConfig.getInt("MoveWeight", 10); - MOVE_WEIGHT_DIAG = geoEngineConfig.getInt("MoveWeightDiag", 14); - OBSTACLE_WEIGHT = geoEngineConfig.getInt("ObstacleWeight", 30); - OBSTACLE_WEIGHT_DIAG = (int) (OBSTACLE_WEIGHT * Math.sqrt(2)); - HEURISTIC_WEIGHT = geoEngineConfig.getInt("HeuristicWeight", 12); - HEURISTIC_WEIGHT_DIAG = geoEngineConfig.getInt("HeuristicWeightDiag", 18); - MAX_ITERATIONS = geoEngineConfig.getInt("MaxIterations", 3500); - PART_OF_CHARACTER_HEIGHT = geoEngineConfig.getInt("PartOfCharacterHeight", 75); - MAX_OBSTACLE_HEIGHT = geoEngineConfig.getInt("MaxObstacleHeight", 32); + GEODATA_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(Config.DATAPACK_ROOT.getPath() + "/" + geoEngineConfig.getString("PathnodePath", "pathnode")); + PATHFINDING = geoEngineConfig.getInt("PathFinding", 0); + PATHFIND_BUFFERS = geoEngineConfig.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = geoEngineConfig.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = geoEngineConfig.getFloat("MediumWeight", 2); + HIGH_WEIGHT = geoEngineConfig.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = geoEngineConfig.getBoolean("AdvancedDiagonalStrategy", true); + DIAGONAL_WEIGHT = geoEngineConfig.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = geoEngineConfig.getInt("MaxPostfilterPasses", 3); + DEBUG_PATH = geoEngineConfig.getBoolean("DebugPath", false); // Load AllowedPlayerRaces config file (if exists) final PropertiesParser allowedPlayerRacesConfig = new PropertiesParser(CUSTOM_ALLOWED_PLAYER_RACES_CONFIG_FILE); diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/enums/GeoType.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/enums/GeoType.java deleted file mode 100644 index f77aa5eac4..0000000000 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/enums/GeoType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -public enum GeoType -{ - L2J("%d_%d.l2j"), - L2OFF("%d_%d_conv.dat"); - - private final String _filename; - - private GeoType(String filename) - { - _filename = filename; - } - - public String getFilename() - { - return _filename; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java deleted file mode 100644 index e62f50a8df..0000000000 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/enums/MoveDirectionType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.enums; - -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; - -/** - * Container of movement constants used for various geodata and movement checks. - */ -public enum MoveDirectionType -{ - N(0, -1), - S(0, 1), - W(-1, 0), - E(1, 0), - NW(-1, -1), - SW(-1, 1), - NE(1, -1), - SE(1, 1); - - // Step and signum. - private final int _stepX; - private final int _stepY; - private final int _signumX; - private final int _signumY; - - // Cell offset. - private final int _offsetX; - private final int _offsetY; - - // Direction flags. - private final byte _directionX; - private final byte _directionY; - private final String _symbolX; - private final String _symbolY; - - private MoveDirectionType(int signumX, int signumY) - { - // Get step (world -16, 0, 16) and signum (geodata -1, 0, 1) coordinates. - _stepX = signumX * GeoStructure.CELL_SIZE; - _stepY = signumY * GeoStructure.CELL_SIZE; - _signumX = signumX; - _signumY = signumY; - - // Get border offsets in a direction of iteration. - _offsetX = signumX >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - _offsetY = signumY >= 0 ? GeoStructure.CELL_SIZE - 1 : 0; - - // Get direction NSWE flag and symbol. - _directionX = signumX < 0 ? GeoStructure.CELL_FLAG_W : signumX == 0 ? 0 : GeoStructure.CELL_FLAG_E; - _directionY = signumY < 0 ? GeoStructure.CELL_FLAG_N : signumY == 0 ? 0 : GeoStructure.CELL_FLAG_S; - _symbolX = signumX < 0 ? "W" : signumX == 0 ? "-" : "E"; - _symbolY = signumY < 0 ? "N" : signumY == 0 ? "-" : "S"; - } - - public int getStepX() - { - return _stepX; - } - - public int getStepY() - { - return _stepY; - } - - public int getSignumX() - { - return _signumX; - } - - public int getSignumY() - { - return _signumY; - } - - public int getOffsetX() - { - return _offsetX; - } - - public int getOffsetY() - { - return _offsetY; - } - - public byte getDirectionX() - { - return _directionX; - } - - public byte getDirectionY() - { - return _directionY; - } - - public String getSymbolX() - { - return _symbolX; - } - - public String getSymbolY() - { - return _symbolY; - } - - /** - * @param gdx : Geodata X delta coordinate. - * @param gdy : Geodata Y delta coordinate. - * @return {@link MoveDirectionType} based on given geodata dx and dy delta coordinates. - */ - public static MoveDirectionType getDirection(int gdx, int gdy) - { - if (gdx == 0) - { - return (gdy < 0) ? MoveDirectionType.N : MoveDirectionType.S; - } - - if (gdy == 0) - { - return (gdx < 0) ? MoveDirectionType.W : MoveDirectionType.E; - } - - if (gdx > 0) - { - return (gdy < 0) ? MoveDirectionType.NE : MoveDirectionType.SE; - } - - return (gdy < 0) ? MoveDirectionType.NW : MoveDirectionType.SW; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java index 7263d256ac..2816a4ca77 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -16,1063 +16,582 @@ */ package org.l2jmobius.gameserver.geoengine; -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.Config; -import org.l2jmobius.commons.util.CommonUtil; import org.l2jmobius.gameserver.data.xml.DoorData; import org.l2jmobius.gameserver.data.xml.FenceData; -import org.l2jmobius.gameserver.enums.GeoType; -import org.l2jmobius.gameserver.enums.MoveDirectionType; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.BlockComplex; -import org.l2jmobius.gameserver.geoengine.geodata.BlockFlat; -import org.l2jmobius.gameserver.geoengine.geodata.BlockMultilayer; -import org.l2jmobius.gameserver.geoengine.geodata.BlockNull; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.GeoData; import org.l2jmobius.gameserver.model.Location; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.WorldObject; -import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; +import org.l2jmobius.gameserver.util.LinePointIterator; +import org.l2jmobius.gameserver.util.LinePointIterator3D; +/** + * GeoEngine. + * @author -Nemesiss-, HorridoJoho + */ public class GeoEngine { - protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); - private final ABlock[][] _blocks; - private final BlockNull _nullBlock; + private static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; - // Pre-allocated buffers. - private final BufferHolder[] _buffers; + private final GeoData _geodata = new GeoData(); - public GeoEngine() + protected GeoEngine() { - LOGGER.info("GeoEngine: Initializing..."); - - // Initialize block container. - _blocks = new ABlock[GeoStructure.GEO_BLOCKS_X][GeoStructure.GEO_BLOCKS_Y]; - - // Load null block. - _nullBlock = new BlockNull(); - - // Initialize multilayer temporarily buffer. - BlockMultilayer.initialize(); - - // Load geo files according to geoengine config setup. - int loaded = 0; + int loadedRegions = 0; try { for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) { for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) { - final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY)); + final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); if (Files.exists(geoFilePath)) { - // Region file is load-able, try to load it. - if (loadGeoBlocks(regionX, regionY)) + try { - loaded++; + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + _geodata.loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); } - } - else - { - // Region file is not load-able, load null blocks. - loadNullBlocks(regionX, regionY); } } } } catch (Exception e) { - LOGGER.warning("GeoEngine: Failed to load geodata! " + e); + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e); System.exit(1); } - LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); - // Release multilayer block temporarily buffer. - BlockMultilayer.release(); - - String[] array = Config.PATHFIND_BUFFERS.split(";"); - _buffers = new BufferHolder[array.length]; - - int count = 0; - for (int i = 0; i < array.length; i++) + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return _geodata.hasGeoPos(geoX, geoY); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return _geodata.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean can = true; + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - String buf = array[i]; - String[] args = buf.split("x"); - - try - { - int size = Integer.parseInt(args[1]); - count += size; - _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); - } - catch (Exception e) - { - LOGGER.warning("Could not load buffer setting:" + buf + ". " + e); - } + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); } - LOGGER.info("Loaded " + count + " node buffers."); - // Avoid wrong configs when no files are loaded. - if ((loaded == 0) && Config.PATHFINDING) + if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) { - Config.PATHFINDING = false; - LOGGER.info("GeoEngine: Forcing PathFinding setting to false."); + // can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH); + can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH); } + + if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + // can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH); + can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return can && checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _geodata.getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + return _geodata.getGeoX(worldX); + } + + public int getGeoY(int worldY) + { + return _geodata.getGeoY(worldY); + } + + public int getWorldX(int geoX) + { + return _geodata.getWorldX(geoX); + } + + public int getWorldY(int geoY) + { + return _geodata.getWorldY(geoY); } /** - * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer and log this situation. - * @param size : pre-calculated minimum required size - * @return NodeBuffer : buffer + * Gets the height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the height */ - private NodeBuffer getBuffer(int size) + public int getHeight(int x, int y, int z) { - NodeBuffer current = null; - for (BufferHolder holder : _buffers) + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height. + * @param x the x coordinate + * @param y the y coordinate + * @param z the the z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) { - // Find proper size of buffer. - if (holder._size < size) + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height. + * @param location the location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Can see target. Doors as target always return true. Checks doors between. + * @param cha the character + * @param target the target + * @return {@code true} if the character can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, WorldObject target) + { + return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceWorld(), target.getX(), target.getY(), target.getZ(), target.getInstanceWorld())); + } + + /** + * Can see target. Checks doors between. + * @param cha the character + * @param worldPosition the world position + * @return {@code true} if the character can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject cha, ILocational worldPosition) + { + return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), cha.getInstanceWorld()); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param instance + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param tInstance the target's instance + * @return + */ + public boolean canSeeTarget(int x, int y, int z, Instance instance, int tx, int ty, int tz, Instance tInstance) + { + return (instance == tInstance) && canSeeTarget(x, y, z, tx, ty, tz, instance); + } + + /** + * Can see target. Checks doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) + { + return false; + } + + return canSeeTarget(x, y, z, tx, ty, tz); + } + + private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions!"); + } + return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ); + } + + /** + * Can see target. Does not check doors between. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int tGeoX = getGeoX(tx); + int tGeoY = getGeoY(ty); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + + // Fastpath. + if ((geoX == tGeoX) && (geoY == tGeoY)) + { + return !hasGeoPos(tGeoX, tGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = tx; + int fromY = ty; + int toX = tx; + int toY = ty; + if (nearestToZ > nearestFromZ) + { + int tmp = toX; + toX = fromX; + fromX = tmp; + + tmp = toY; + toY = fromY; + fromY = tmp; + + tmp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = tmp; + + tmp = tGeoX; + tGeoX = geoX; + geoX = tmp; + + tmp = tGeoY; + tGeoY = geoY; + geoY = tmp; + } + + final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, nearestFromZ, tGeoX, tGeoY, nearestToZ); + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + final int prevZ = pointIter.z(); + int prevGeoZ = prevZ; + int ptIndex = 0; + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + + if ((curX == prevX) && (curY == prevY)) { continue; } - // Get NodeBuffer. - current = holder.getBuffer(); - if (current != null) - { - return current; - } - } - - return current; - } - - /** - * Loads geodata from a file. When file does not exist, is corrupted or not consistent, loads none geodata. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - * @return boolean : True, when geodata file was loaded without problem. - */ - private boolean loadGeoBlocks(int regionX, int regionY) - { - final String filename = String.format(Config.GEODATA_TYPE.getFilename(), regionX, regionY); - final String filepath = Config.GEODATA_PATH + File.separator + filename; - - // Standard load. - try (RandomAccessFile raf = new RandomAccessFile(filepath, "r"); - FileChannel fc = raf.getChannel()) - { - // Initialize file buffer. - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final int beeCurZ = pointIter.z(); + int curGeoZ = prevGeoZ; - // Load 18B header for L2OFF geodata (1st and 2nd byte...region X and Y). - if (Config.GEODATA_TYPE == GeoType.L2OFF) + // Check if the position has geodata. + if (hasGeoPos(curX, curY)) { - for (int i = 0; i < 18; i++) + final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); + curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); + final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (curGeoZ <= maxHeight) { - buffer.get(); - } - } - - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Loop over region blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - if (Config.GEODATA_TYPE == GeoType.L2J) + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) { - // Get block type. - final byte type = buffer.get(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - case GeoStructure.TYPE_MULTILAYER_L2J: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - default: - { - throw new IllegalArgumentException("Unknown block type: " + type); - } - } + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ)); } else { - // Get block type. - final short type = buffer.getShort(); - - // Load block according to block type. - switch (type) - { - case GeoStructure.TYPE_FLAT_L2J_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, Config.GEODATA_TYPE); - break; - } - case GeoStructure.TYPE_COMPLEX_L2OFF: - { - _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer); - break; - } - default: - { - _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, Config.GEODATA_TYPE); - break; - } - } + canSeeThrough = true; } } + + if (!canSeeThrough) + { + return false; + } } - // Check data consistency. - if (buffer.remaining() > 0) - { - LOGGER.warning("Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); - } - - // Loading was successful. - return true; - } - catch (Exception e) - { - // An error occured while loading, load null blocks. - LOGGER.warning("Error loading " + filename + " region file. " + e); - - // Replace whole region file with null blocks. - loadNullBlocks(regionX, regionY); - - // Loading was not successful. - return false; - } - } - - /** - * Loads null blocks. Used when no region file is detected or an error occurs during loading. - * @param regionX : Geodata file region X coordinate. - * @param regionY : Geodata file region Y coordinate. - */ - private void loadNullBlocks(int regionX, int regionY) - { - // Get block indexes. - final int blockX = (regionX - World.TILE_X_MIN) * GeoStructure.REGION_BLOCKS_X; - final int blockY = (regionY - World.TILE_Y_MIN) * GeoStructure.REGION_BLOCKS_Y; - - // Load all null blocks. - for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) - { - for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) - { - _blocks[blockX + ix][blockY + iy] = _nullBlock; - } - } - } - - /** - * Converts world X to geodata X. - * @param worldX - * @return int : Geo X - */ - public static int getGeoX(int worldX) - { - return (worldX - World.WORLD_X_MIN) >> 4; - } - - /** - * Converts world Y to geodata Y. - * @param worldY - * @return int : Geo Y - */ - public static int getGeoY(int worldY) - { - return (worldY - World.WORLD_Y_MIN) >> 4; - } - - /** - * Converts geodata X to world X. - * @param geoX - * @return int : World X - */ - public static int getWorldX(int geoX) - { - return (geoX << 4) + World.WORLD_X_MIN + 8; - } - - /** - * Converts geodata Y to world Y. - * @param geoY - * @return int : World Y - */ - public static int getWorldY(int geoY) - { - return (geoY << 4) + World.WORLD_Y_MIN + 8; - } - - /** - * Returns block of geodata on given coordinates. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return {@link ABlock} : Block of geodata. - */ - public ABlock getBlock(int geoX, int geoY) - { - final int x = geoX / GeoStructure.BLOCK_CELLS_X; - if ((x < 0) || (x >= GeoStructure.GEO_BLOCKS_X)) - { - return null; - } - final int y = geoY / GeoStructure.BLOCK_CELLS_Y; - if ((y < 0) || (y >= GeoStructure.GEO_BLOCKS_Y)) - { - return null; - } - return _blocks[x][y]; - } - - /** - * Check if geo coordinates has geo. - * @param geoX : Geodata X - * @param geoY : Geodata Y - * @return boolean : True, if given geo coordinates have geodata - */ - public boolean hasGeoPos(int geoX, int geoY) - { - final ABlock block = getBlock(geoX, geoY); - return (block != null) && block.hasGeoPos(); - } - - /** - * Returns the height of cell, which is closest to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, closest to given coordinates. - */ - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return (short) worldZ; - } - return block.getHeightNearest(geoX, geoY, worldZ); - } - - /** - * Returns the NSWE flag byte of cell, which is closes to given coordinates. - * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte coordinate, closest to given coordinates. - */ - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - final ABlock block = getBlock(geoX, geoY); - if (block == null) - { - return GeoStructure.CELL_FLAG_ALL; - } - return block.getNsweNearest(geoX, geoY, worldZ); - } - - /** - * Check if world coordinates has geo. - * @param worldX : World X - * @param worldY : World Y - * @return boolean : True, if given world coordinates have geodata - */ - public boolean hasGeo(int worldX, int worldY) - { - return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param loc : The location used as reference. - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(Location loc) - { - return getHeightNearest(getGeoX(loc.getX()), getGeoY(loc.getY()), loc.getZ()); - } - - /** - * Returns closest Z coordinate according to geodata. - * @param worldX : world x - * @param worldY : world y - * @param worldZ : world z - * @return short : nearest Z coordinates according to geodata - */ - public short getHeight(int worldX, int worldY, int worldZ) - { - return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); - } - - /** - * Check line of sight from {@link WorldObject} to {@link WorldObject}.
- * @param object : The origin object. - * @param target : The target object. - * @return True, when object can see target. - */ - public boolean canSeeTarget(WorldObject object, WorldObject target) - { - // Can always see doors. - if (target.isDoor()) - { - return true; + prevX = curX; + prevY = curY; + prevGeoZ = curGeoZ; + ++ptIndex; } - if (object.getInstanceWorld() != target.getInstanceWorld()) - { - return false; - } - - if (DoorData.getInstance().checkIfDoorsBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(object.getX(), object.getY(), object.getZ(), target.getX(), target.getY(), target.getZ(), object.getInstanceWorld())) - { - return false; - } - - // Get object's and target's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - double theight = 0; - if (target instanceof Creature) - { - theight += (((Creature) target).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - return canSee(object.getX(), object.getY(), object.getZ(), oheight, target.getX(), target.getY(), target.getZ(), theight) && canSee(target.getX(), target.getY(), target.getZ(), theight, object.getX(), object.getY(), object.getZ(), oheight); - } - - /** - * Check line of sight from {@link WorldObject} to {@link Location}.
- * Note: The check uses {@link Location}'s real Z coordinate (e.g. point above ground), not its geodata representation. - * @param object : The origin object. - * @param position : The target position. - * @return True, when object can see position. - */ - public boolean canSeeLocation(WorldObject object, Location position) - { - // Get object and location coordinates. - int ox = object.getX(); - int oy = object.getY(); - int oz = object.getZ(); - int tx = position.getX(); - int ty = position.getY(); - int tz = position.getZ(); - - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld(), false)) - { - return false; - } - - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, object.getInstanceWorld())) - { - return false; - } - - // Get object's line of sight height (if relevant). - // Note: real creature height = collision height * 2 - double oheight = 0; - if (object instanceof Creature) - { - oheight += (((Creature) object).getCollisionHeight() * 2 * Config.PART_OF_CHARACTER_HEIGHT) / 100; - } - - // Perform geodata check. - return canSee(ox, oy, oz, oheight, tx, ty, tz, 0) && canSee(tx, ty, tz, 0, ox, oy, oz, oheight); - } - - /** - * Simple check for origin to target visibility.
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param oheight : The height of origin, used as start point. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param theight : The height of target, used as end point. - * @return True, when origin can see target. - */ - public boolean canSee(int ox, int oy, int oz, double oheight, int tx, int ty, int tz, double theight) - { - // Get origin geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get target geodata coordinates. - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check being on same cell and layer (index). - // Note: Get index must use origin height increased by cell height, the method returns index to height exclusive self. - int index = block.getIndexNearest(gox, goy, oz + GeoStructure.CELL_HEIGHT); // getIndexBelow - if (index < 0) - { - return false; - } - - if ((gox == gtx) && (goy == gty)) - { - return index == block.getIndexNearest(gtx, gty, tz + GeoStructure.CELL_HEIGHT); // getIndexBelow - } - - // Get ground and nswe flag. - int groundZ = block.getHeight(index); - int nswe = block.getNswe(index); - - // Get delta coordinates, slope of line (XY, XZ) and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double dz = (tz + theight) - (oz + oheight); - final double m = (double) dy / dx; - final double mz = dz / Math.sqrt((dx * dx) + (dy * dy)); - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell in X direction. - gridX += mdt.getStepX(); - gox += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - goy += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Get block of the next cell. - block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Get line of sight height (including Z slope). - double losz = oz + oheight + Config.MAX_OBSTACLE_HEIGHT; - losz += mz * Math.sqrt(((checkX - ox) * (checkX - ox)) + ((checkY - oy) * (checkY - oy))); - - // Check line of sight going though wall (vertical check). - - // Get index of particular layer, based on last iterated cell conditions. - boolean canMove = (nswe & dir) != 0; - if (canMove) - { - // No wall present, get next cell below current cell. - index = block.getIndexBelow(gox, goy, groundZ + GeoStructure.CELL_IGNORE_HEIGHT); - } - else - { - // Wall present, get next cell above current cell. - index = block.getIndexAbove(gox, goy, groundZ - (2 * GeoStructure.CELL_HEIGHT)); - } - - // Next cell's does not exist (no geodata with valid condition), return fail. - if (index < 0) - { - return false; - } - - // Get next cell's layer height. - int z = block.getHeight(index); - - // Perform sine of sight check (next cell is above line of sight line), return fail. - if (!canMove && (z > losz)) - { - return false; - } - - // Next cell is accessible, update z and NSWE. - groundZ = z; - nswe = block.getNswe(index); - } - - // Iteration is completed, no obstacle is found. return true; } /** - * Check movement from coordinates to coordinates.
- * Note: The Z coordinates are supposed to be already validated geodata coordinates. - * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return True, when target coordinates are reachable from origin coordinates. + * Verifies if the is a path between origin's location and destination, if not returns the closest location. + * @param origin the origin + * @param destination the destination + * @return the destination if there is a path or the closes location */ - public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public Location getValidLocation(ILocational origin, ILocational destination) { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), null); + } + + /** + * Move check. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param tx the target's x coordinate + * @param ty the target's y coordinate + * @param tz the target's z coordinate + * @param instance the instance + * @return the last Location (x,y,z) where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int tx, int ty, int tz, Instance instance) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, tz); + // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, tx, ty, nearestToZ, instance)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // first point is guaranteed to be available + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) + { + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) + { + // Can't move, return previous location. + return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); + } + prevX = curX; + prevY = curY; + prevZ = curZ; + } + return hasGeoPos(prevX, prevY) && (prevZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(tx, ty, nearestToZ); + } + + /** + * Checks if its possible to move from one location to another. + * @param fromX the X coordinate to start checking from + * @param fromY the Y coordinate to start checking from + * @param fromZ the Z coordinate to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @param instance the instance + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, Instance instance) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int tGeoX = getGeoX(toX); + final int tGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(tGeoX, tGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance, false)) { return false; } // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instance)) { return false; } - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevX = pointIter.x(); + int prevY = pointIter.y(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return true; // No Geodata found. - } - int goz = getHeightNearest(gox, goy, oz); - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - - // Check movement within same cell. - if ((gox == gtx) && (goy == gty)) - { - return goz == getHeight(tx, ty, tz); - } - - // Get nswe flag. - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid X coordinate. - int gridX = ox & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - final int checkX = gridX + mdt.getOffsetX(); - final int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Set next cell in Y direction. - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check point heading into obstacle, if so return current point. - if ((nswe & dir) == 0) + final int curX = pointIter.x(); + final int curY = pointIter.y(); + final int curZ = getNearestZ(curX, curY, prevZ); + if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY))) { return false; } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return true; // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return false; - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); + prevX = curX; + prevY = curY; + prevZ = curZ; } - - // When origin Z is target Z, the move is successful. - return goz == getHeight(tx, ty, tz); + return !hasGeoPos(prevX, prevY) || (prevZ == nearestToZ); } - /** - * Check movement from origin to target coordinates. Returns last available point in the checked path.
- * Target X and Y reachable and Z is on same floor: - *
    - *
  • Location of the target with corrected Z value from geodata.
  • - *
- * Target X and Y reachable but Z is on another floor: - *
    - *
  • Location of the origin with corrected Z value from geodata.
  • - *
- * Target X and Y not reachable: - *
    - *
  • Last accessible location in destination to target.
  • - *
- * @param ox : Origin X coordinate. - * @param oy : Origin Y coordinate. - * @param oz : Origin Z coordinate. - * @param tx : Target X coordinate. - * @param ty : Target Y coordinate. - * @param tz : Target Z coordinate. - * @param instance - * @return The {@link Location} representing last point of movement (e.g. just before wall). - */ - public Location getValidLocation(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public int traceTerrainZ(int x, int y, int z1, int tx, int ty) { - // Door checks. - if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z1); + final int tGeoX = getGeoX(tx); + final int tGeoY = getGeoY(ty); + + final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY); + // First point is guaranteed to be available. + pointIter.next(); + int prevZ = nearestFromZ; + + while (pointIter.next()) { - return new Location(ox, oy, oz); + prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ); } - // Fence checks. - if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) - { - return new Location(ox, oy, oz); - } - - // Get geodata coordinates. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - ABlock block = getBlock(gox, goy); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - final int gtx = getGeoX(tx); - final int gty = getGeoY(ty); - final int gtz = getHeightNearest(gtx, gty, tz); - int goz = getHeightNearest(gox, goy, oz); - int nswe = getNsweNearest(gox, goy, goz); - - // Get delta coordinates, slope of line and direction data. - final int dx = tx - ox; - final int dy = ty - oy; - final double m = (double) dy / dx; - final MoveDirectionType mdt = MoveDirectionType.getDirection(gtx - gox, gty - goy); - - // Get cell grid coordinates. - int gridX = ox & 0xFFFFFFF0; - int gridY = oy & 0xFFFFFFF0; - - // Run loop. - byte dir; - int nx = gox; - int ny = goy; - while ((gox != gtx) || (goy != gty)) - { - // Calculate intersection with cell's X border. - int checkX = gridX + mdt.getOffsetX(); - int checkY = (int) (oy + (m * (checkX - ox))); - - if ((mdt.getStepX() != 0) && (getGeoY(checkY) == goy)) - { - // Set next cell is in X direction. - gridX += mdt.getStepX(); - nx += mdt.getSignumX(); - dir = mdt.getDirectionX(); - } - else - { - // Calculate intersection with cell's Y border. - checkY = gridY + mdt.getOffsetY(); - checkX = (int) (ox + ((checkY - oy) / m)); - checkX = CommonUtil.limit(checkX, gridX, gridX + 15); - - // Set next cell in Y direction. - gridY += mdt.getStepY(); - ny += mdt.getSignumY(); - dir = mdt.getDirectionY(); - } - - // Check target cell is out of geodata grid (world coordinates). - if ((nx < 0) || (nx >= GeoStructure.GEO_CELLS_X) || (ny < 0) || (ny >= GeoStructure.GEO_CELLS_Y)) - { - return new Location(checkX, checkY, goz); - } - - // Check point heading into obstacle, if so return current (border) point. - if ((nswe & dir) == 0) - { - return new Location(checkX, checkY, goz); - } - - block = getBlock(nx, ny); - if ((block == null) || !block.hasGeoPos()) - { - return new Location(tx, ty, tz); // No Geodata found. - } - - // Check next point for extensive Z difference, if so return current (border) point. - final int i = block.getIndexBelow(nx, ny, goz + GeoStructure.CELL_IGNORE_HEIGHT); - if (i < 0) - { - return new Location(checkX, checkY, goz); - } - - // Update current point's coordinates and nswe. - gox = nx; - goy = ny; - goz = block.getHeight(i); - nswe = block.getNswe(i); - } - - // Compare Z coordinates: - // If same, path is okay, return target point and fix its Z geodata coordinate. - // If not same, path is does not exist, return origin point. - return goz == gtz ? new Location(tx, ty, gtz) : new Location(ox, oy, oz); + return prevZ; } /** - * Returns the list of location objects as a result of complete path calculation. - * @param ox : origin x - * @param oy : origin y - * @param oz : origin z - * @param tx : target x - * @param ty : target y - * @param tz : target z - * @param instance - * @return {@code List} : complete path from nodes + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param toX the X coordinate to end checking at + * @param toY the Y coordinate to end checking at + * @param toZ the Z coordinate to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) { - // Get origin and check existing geo coords. - int gox = getGeoX(ox); - int goy = getGeoY(oy); - if (!hasGeoPos(gox, goy)) - { - return Collections.emptyList(); - } - - int goz = getHeightNearest(gox, goy, oz); - - // Get target and check existing geo coords. - int gtx = getGeoX(tx); - int gty = getGeoY(ty); - if (!hasGeoPos(gtx, gty)) - { - return Collections.emptyList(); - } - - int gtz = getHeightNearest(gtx, gty, tz); - - // Prepare buffer for pathfinding calculations. - int dx = Math.abs(gox - gtx); - int dy = Math.abs(goy - gty); - int dz = Math.abs(goz - gtz) / 8; - int total = dx + dy + dz; - int size = 1000 + (10 * total); - NodeBuffer buffer = getBuffer(size); - if (buffer == null) - { - return Collections.emptyList(); - } - - // Find path. - List path = null; - try - { - path = buffer.findPath(gox, goy, goz, gtx, gty, gtz); - if (path.isEmpty()) - { - return Collections.emptyList(); - } - } - catch (Exception e) - { - return Collections.emptyList(); - } - finally - { - buffer.free(); - } - - // Check path. - if (path.size() < 3) - { - return path; - } - - // Get path list iterator. - ListIterator point = path.listIterator(); - - // Get node A (origin). - int nodeAx = ox; - int nodeAy = oy; - int nodeAz = goz; - - // Get node B. - Location nodeB = point.next(); - - // Iterate thought the path to optimize it. - while (point.hasNext()) - { - // Get node C. - Location nodeC = path.get(point.nextIndex()); - - // Check movement from node A to node C. - if (canMoveToTarget(nodeAx, nodeAy, nodeAz, nodeC.getX(), nodeC.getY(), nodeC.getZ(), instance)) - { - // Can move from node A to node C. - // Remove node B. - point.remove(); - } - else - { - // Can not move from node A to node C. - // Set node A (node B is part of path, update A coordinates). - nodeAx = nodeB.getX(); - nodeAy = nodeB.getY(); - nodeAz = nodeB.getZ(); - } - - // Set node B. - nodeB = point.next(); - } - - return path; + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, null); } /** - * NodeBuffer container with specified size and count of separate buffers. + * Checks if its possible to move from one location to another. + * @param from the {@code ILocational} to start checking from + * @param to the {@code ILocational} to end checking at + * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise */ - private static class BufferHolder + public boolean canMoveToTarget(ILocational from, ILocational to) { - final int _size; - final int _count; - final Set _buffer; - - public BufferHolder(int size, int count) - { - _size = size; - _count = count * 4; - _buffer = ConcurrentHashMap.newKeySet(_count); - - for (int i = 0; i < count; i++) - { - _buffer.add(new NodeBuffer(size)); - } - } - - public NodeBuffer getBuffer() - { - // Get available free NodeBuffer. - for (NodeBuffer buffer : _buffer) - { - if (!buffer.isLocked()) - { - continue; - } - - return buffer; - } - - // No free NodeBuffer found, try allocate new buffer. - if (_buffer.size() < _count) - { - NodeBuffer buffer = new NodeBuffer(_size); - buffer.isLocked(); - _buffer.add(buffer); - - if (_buffer.size() == _count) - { - LOGGER.warning("NodeBuffer holder with " + _size + " size reached max capacity."); - } - - return buffer; - } - - return null; - } + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); } /** - * Returns the instance of the {@link GeoEngine}. - * @return {@link GeoEngine} : The instance. + * Checks the specified position for available geodata. + * @param x the X coordinate + * @param y the Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + public static GeoEngine getInstance() { - return SingletonHolder.INSTANCE; + return SingletonHolder._instance; } private static class SingletonHolder { - protected static final GeoEngine INSTANCE = new GeoEngine(); + protected static final GeoEngine _instance = new GeoEngine(); } -} \ No newline at end of file +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java deleted file mode 100644 index 49ec35aa1c..0000000000 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public abstract class ABlock -{ - /** - * Checks the block for having geodata. - * @return boolean : True, when block has geodata (Flat, Complex, Multilayer). - */ - public abstract boolean hasGeoPos(); - - /** - * Returns the height of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell geodata Z coordinate, nearest to given coordinates. - */ - public abstract short getHeightNearest(int geoX, int geoY, int worldZ); - - /** - * Returns the NSWE flag byte of cell, which is closest to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return short : Cell NSWE flag byte, nearest to given coordinates. - */ - public abstract byte getNsweNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is closes layer to given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. - */ - public abstract int getIndexNearest(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer above given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexAbove(int geoX, int geoY, int worldZ); - - /** - * Returns index to data of the cell, which is first layer below given coordinates.
- * @param geoX : Cell geodata X coordinate. - * @param geoY : Cell geodata Y coordinate. - * @param worldZ : Cell world Z coordinate. - * @return {@code int} : Cell index. -1..when no layer available below given Z coordinate. - */ - public abstract int getIndexBelow(int geoX, int geoY, int worldZ); - - /** - * Returns the height of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract short getHeight(int index); - - /** - * Returns the NSWE flag byte of cell given by cell index.
- * @param index : Index of the cell. - * @return short : Cell geodata Z coordinate, below given coordinates. - */ - public abstract byte getNswe(int index); -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java deleted file mode 100644 index f5997bf287..0000000000 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -public class BlockComplex extends ABlock -{ - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockComplex() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates ComplexBlock. - * @param bb : Input byte buffer. - */ - public BlockComplex(ByteBuffer bb) - { - // Initialize buffer. - _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; - - // Load data. - for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) - { - // Get data. - short data = bb.getShort(); - - // Get nswe. - _buffer[i * 3] = (byte) (data & 0x000F); - - // Get height. - data = (short) ((short) (data & 0xFFF0) >> 1); - _buffer[(i * 3) + 1] = (byte) (data & 0x00FF); - _buffer[(i * 3) + 2] = (byte) (data >> 8); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get nswe. - return _buffer[index]; - } - - @Override - public final int getIndexNearest(int geoX, int geoY, int worldZ) - { - return (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height > worldZ ? index : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)) * 3; - - // Get height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Check height and return nswe. - return height < worldZ ? index : -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java deleted file mode 100644 index 9af9d2a28a..0000000000 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockFlat extends ABlock -{ - protected final short _height; - protected byte _nswe; - - /** - * Creates FlatBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockFlat(ByteBuffer bb, GeoType type) - { - // Get height and nswe. - _height = bb.getShort(); - _nswe = GeoStructure.CELL_FLAG_ALL; - - // Read dummy data. - if (type == GeoType.L2OFF) - { - bb.getShort(); - } - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return _height; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return _nswe; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height > worldZ ? 0 : -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Check height and return index. - return _height < worldZ ? 0 : -1; - } - - @Override - public short getHeight(int index) - { - return _height; - } - - @Override - public byte getNswe(int index) - { - return _nswe; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java deleted file mode 100644 index 31fbf5d477..0000000000 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.l2jmobius.gameserver.enums.GeoType; - -public class BlockMultilayer extends ABlock -{ - private static final int MAX_LAYERS = Byte.MAX_VALUE; - - private static ByteBuffer _temp; - - /** - * Initializes the temporary buffer. - */ - public static final void initialize() - { - // Initialize temporary buffer and sorting mechanism. - _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); - _temp.order(ByteOrder.LITTLE_ENDIAN); - } - - /** - * Releases temporary buffer. - */ - public static final void release() - { - _temp = null; - } - - protected byte[] _buffer; - - /** - * Implicit constructor for children class. - */ - protected BlockMultilayer() - { - // Buffer is initialized in children class. - _buffer = null; - } - - /** - * Creates MultilayerBlock. - * @param bb : Input byte buffer. - * @param type : The type of loaded geodata. - */ - public BlockMultilayer(ByteBuffer bb, GeoType type) - { - // Move buffer pointer to end of MultilayerBlock. - for (int cell = 0; cell < GeoStructure.BLOCK_CELLS; cell++) - { - // Get layer count for this cell. - final byte layers = type != GeoType.L2OFF ? bb.get() : (byte) bb.getShort(); - - if ((layers <= 0) || (layers > MAX_LAYERS)) - { - throw new RuntimeException("Invalid layer count for MultilayerBlock"); - } - - // Add layers count. - _temp.put(layers); - - // Loop over layers. - for (byte layer = 0; layer < layers; layer++) - { - // Get data. - final short data = bb.getShort(); - - // Add nswe and height. - _temp.put((byte) (data & 0x000F)); - _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); - } - } - - // Initialize buffer. - _buffer = Arrays.copyOf(_temp.array(), _temp.position()); - - // Clear temp buffer. - _temp.clear(); - } - - @Override - public boolean hasGeoPos() - { - return true; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - // Get cell index. - final int index = getIndexNearest(geoX, geoY, worldZ); - - // Get nswe. - return _buffer[index]; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - - // Loop though all cell layers, find closest layer to given worldZ. - int limit = Integer.MAX_VALUE; - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Get Z distance and compare with limit. - // Note: When 2 layers have same distance to worldZ (worldZ is in the middle of them): - // > Returns bottom layer. - // >= Returns upper layer. - final int distance = Math.abs(height - worldZ); - if (distance > limit) - { - break; - } - - // Update limit and move to next layer. - limit = distance; - index += 3; - } - - // Return layer index. - return index - 3; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to last layer data (first from bottom). - byte layers = _buffer[index++]; - index += (layers - 1) * 3; - - // Loop though all layers, find first layer above worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is higher than worldZ, return layer index. - if (height > worldZ) - { - return index; - } - - // Move index to next layer. - index -= 3; - } - - // No layer found. - return -1; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - // Move index to the cell given by coordinates. - int index = 0; - for (int i = 0; i < (((geoX % GeoStructure.BLOCK_CELLS_X) * GeoStructure.BLOCK_CELLS_Y) + (geoY % GeoStructure.BLOCK_CELLS_Y)); i++) - { - // Move index by amount of layers for this cell. - index += (_buffer[index] * 3) + 1; - } - - // Get layers count and shift to first layer data (first from top). - byte layers = _buffer[index++]; - - // Loop though all layers, find first layer below worldZ. - while (layers-- > 0) - { - // Get layer height. - final int height = (_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8); - - // Layer height is lower than worldZ, return layer index. - if (height < worldZ) - { - return index; - } - - // Move index to next layer. - index += 3; - } - - // No layer found. - return -1; - } - - @Override - public short getHeight(int index) - { - // Get height. - return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); - } - - @Override - public byte getNswe(int index) - { - // Get nswe. - return _buffer[index]; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java deleted file mode 100644 index f77d21d84b..0000000000 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -public class BlockNull extends ABlock -{ - @Override - public boolean hasGeoPos() - { - return false; - } - - @Override - public short getHeightNearest(int geoX, int geoY, int worldZ) - { - return (short) worldZ; - } - - @Override - public byte getNsweNearest(int geoX, int geoY, int worldZ) - { - return GeoStructure.CELL_FLAG_ALL; - } - - @Override - public int getIndexNearest(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexAbove(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public int getIndexBelow(int geoX, int geoY, int worldZ) - { - return 0; - } - - @Override - public short getHeight(int index) - { - return 0; - } - - @Override - public byte getNswe(int index) - { - return GeoStructure.CELL_FLAG_ALL; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000000..61ea4fcf82 --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public final class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java new file mode 100644 index 0000000000..f67aaf948b --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/GeoData.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; + +/** + * @author HorridoJoho + */ +public final class GeoData +{ + // world dimensions: 1048576 * 1048576 = 1099511627776 + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MAX_X = 393215; + private static final int WORLD_MIN_Y = -589824; + private static final int WORLD_MAX_Y = 458751; + + /** Regions in the world on the x axis */ + public static final int GEO_REGIONS_X = 32; + /** Regions in the world on the y axis */ + public static final int GEO_REGIONS_Y = 32; + /** Region in the world */ + public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + + /** Blocks in the world on the x axis */ + public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X; + /** Blocks in the world on the y axis */ + public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y; + /** Blocks in the world */ + public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS; + + /** Cells in the world on the x axis */ + public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in the world in the y axis */ + public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + + /** The regions array */ + private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); + + public GeoData() + { + for (int i = 0; i < _regions.length(); i++) + { + _regions.set(i, NullRegion.INSTANCE); + } + } + + private void checkGeoX(int geoX) + { + if ((geoX < 0) || (geoX >= GEO_CELLS_X)) + { + throw new IllegalArgumentException(); + } + } + + private void checkGeoY(int geoY) + { + if ((geoY < 0) || (geoY >= GEO_CELLS_Y)) + { + throw new IllegalArgumentException(); + } + } + + private IRegion getRegion(int geoX, int geoY) + { + checkGeoX(geoX); + checkGeoY(geoY); + return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + public void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) + { + _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + public void unloadRegion(int regionX, int regionY) + { + _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); + } + + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + public int getGeoX(int worldX) + { + if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X)) + { + throw new IllegalArgumentException(); + } + return (worldX - WORLD_MIN_X) / 16; + } + + public int getGeoY(int worldY) + { + if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y)) + { + throw new IllegalArgumentException(); + } + return (worldY - WORLD_MIN_Y) / 16; + } + + public int getWorldX(int geoX) + { + checkGeoX(geoX); + return (geoX * 16) + WORLD_MIN_X + 8; + } + + public int getWorldY(int geoY) + { + checkGeoY(geoY); + return (geoY * 16) + WORLD_MIN_Y + 8; + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java deleted file mode 100644 index 87073479af..0000000000 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.geodata; - -import org.l2jmobius.gameserver.model.World; - -public final class GeoStructure -{ - // Geo cell direction (nswe) flags. - public static final byte CELL_FLAG_NONE = 0x00; - public static final byte CELL_FLAG_E = 0x01; - public static final byte CELL_FLAG_W = 0x02; - public static final byte CELL_FLAG_S = 0x04; - public static final byte CELL_FLAG_N = 0x08; - public static final byte CELL_FLAG_SE = 0x10; - public static final byte CELL_FLAG_SW = 0x20; - public static final byte CELL_FLAG_NE = 0x40; - public static final byte CELL_FLAG_NW = (byte) 0x80; - public static final byte CELL_FLAG_ALL = 0x0F; - - // Geo cell expansion flags - public static final byte CELL_EXPANSION_E = CELL_FLAG_E | CELL_FLAG_NE | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_W = CELL_FLAG_W | CELL_FLAG_NW | CELL_FLAG_SW; - public static final byte CELL_EXPANSION_S = CELL_FLAG_S | CELL_FLAG_SW | CELL_FLAG_SE; - public static final byte CELL_EXPANSION_N = CELL_FLAG_N | CELL_FLAG_NW | CELL_FLAG_NE; - public static final byte CELL_EXPANSION_SE = CELL_FLAG_SE | CELL_FLAG_S | CELL_FLAG_E; - public static final byte CELL_EXPANSION_SW = CELL_FLAG_SW | CELL_FLAG_S | CELL_FLAG_W; - public static final byte CELL_EXPANSION_NE = CELL_FLAG_NE | CELL_FLAG_N | CELL_FLAG_E; - public static final byte CELL_EXPANSION_NW = CELL_FLAG_NW | CELL_FLAG_N | CELL_FLAG_W; - // public static final byte CELL_EXPANSION_MASK = CELL_FLAG_SE | CELL_FLAG_SW | CELL_FLAG_NE | CELL_FLAG_NW; - public static final byte CELL_EXPANSION_ALL = (byte) 0xFF; - - // Geo cell height constants. - public static final int CELL_SIZE = 16; - public static final int CELL_HEIGHT = 8; - public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; - - // Geo block type identification. - public static final byte TYPE_FLAT_L2J_L2OFF = 0; - public static final byte TYPE_COMPLEX_L2J = 1; - public static final byte TYPE_COMPLEX_L2OFF = 0x40; - public static final byte TYPE_MULTILAYER_L2J = 2; - // public static final byte TYPE_MULTILAYER_L2OFF = 0x41; // officially not does exist, is anything above complex block (0x41 - 0xFFFF) - - // Geo block dimensions. - public static final int BLOCK_CELLS_X = 8; - public static final int BLOCK_CELLS_Y = 8; - public static final int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; - - // Geo region dimensions. - public static final int REGION_BLOCKS_X = 256; - public static final int REGION_BLOCKS_Y = 256; - public static final int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; - - public static final int REGION_CELLS_X = REGION_BLOCKS_X * BLOCK_CELLS_X; - public static final int REGION_CELLS_Y = REGION_BLOCKS_Y * BLOCK_CELLS_Y; - - // Geo world dimensions. - public static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); - public static final int GEO_REGIONS_Y = ((World.TILE_Y_MAX - World.TILE_Y_MIN) + 1); - - public static final int GEO_BLOCKS_X = GEO_REGIONS_X * REGION_BLOCKS_X; - public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * REGION_BLOCKS_Y; - - public static final int GEO_CELLS_X = GEO_BLOCKS_X * BLOCK_CELLS_X; - public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * BLOCK_CELLS_Y; -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000000..25eafc5a04 --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000000..0d2c765002 --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a regioin on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000000..544adfea8f --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public final class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000000..293272bd59 --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000000..3de69f3145 --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000F); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000000..f0296ff201 --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho + */ +public final class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000000..b7263ee6cd --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,98 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho + */ +public final class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java new file mode 100644 index 0000000000..673af824d0 --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +public abstract class AbstractNode +{ + private Loc _loc; + private AbstractNode _parent; + + public AbstractNode(Loc loc) + { + _loc = loc; + } + + public void setParent(AbstractNode p) + { + _parent = p; + } + + public AbstractNode getParent() + { + return _parent; + } + + public Loc getLoc() + { + return _loc; + } + + public void setLoc(Loc l) + { + _loc = l; + } + + @Override + public int hashCode() + { + return (31 * 1) + ((_loc == null) ? 0 : _loc.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof AbstractNode)) + { + return false; + } + final AbstractNode other = (AbstractNode) obj; + if (_loc == null) + { + if (other._loc != null) + { + return false; + } + } + else if (!_loc.equals(other._loc)) + { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java new file mode 100644 index 0000000000..7ef07ab22c --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java @@ -0,0 +1,35 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +/** + * @author -Nemesiss- + */ +public abstract class AbstractNodeLoc +{ + public abstract int getX(); + + public abstract int getY(); + + public abstract int getZ(); + + public abstract void setZ(short z); + + public abstract int getNodeX(); + + public abstract int getNodeY(); +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java deleted file mode 100644 index b837e2007a..0000000000 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class Node extends Location implements Comparable -{ - // Node geodata values. - private int _geoX; - private int _geoY; - private byte _nswe; - private byte _nsweExpand; - - // The cost G (movement cost done) and cost H (estimated cost to target). - private int _costG; - private int _costH; - private int _costF; - - // Node parent (reverse path construction). - private Node _parent; - - public Node() - { - super(0, 0, 0); - } - - @Override - public void clean() - { - super.clean(); - - _geoX = 0; - _geoY = 0; - _nswe = GeoStructure.CELL_FLAG_NONE; - _nsweExpand = GeoStructure.CELL_FLAG_NONE; - - _costG = 0; - _costH = 0; - _costF = 0; - - _parent = null; - } - - public void setGeo(int gx, int gy, int gz, byte nswe, byte nsweExpand) - { - super.setXYZ(GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), gz); - - _geoX = gx; - _geoY = gy; - _nswe = nswe; - _nsweExpand = nsweExpand; - } - - public void setCost(Node parent, int weight, int costH) - { - _costG = weight; - if (parent != null) - { - _costG += parent._costG; - } - _costH = costH; - _costF = _costG + _costH; - - _parent = parent; - } - - public int getGeoX() - { - return _geoX; - } - - public int getGeoY() - { - return _geoY; - } - - public byte getNSWE() - { - return _nswe; - } - - public byte getNsweExpand() - { - return _nsweExpand; - } - - public int getCostF() - { - return _costF; - } - - public Node getParent() - { - return _parent; - } - - @Override - public int compareTo(Node o) - { - return _costF - o._costF; - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java deleted file mode 100644 index 8c23e26fad..0000000000 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * 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 org.l2jmobius.gameserver.geoengine.pathfinding; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.locks.ReentrantLock; - -import org.l2jmobius.Config; -import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.ABlock; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; -import org.l2jmobius.gameserver.model.Location; - -public class NodeBuffer -{ - // Locking NodeBuffer to ensure thread-safe operations. - private final ReentrantLock _lock = new ReentrantLock(); - - // Container holding all available Nodes to be used. - private final Node[] _buffer; - private int _bufferIndex; - // Container (binary-heap) holding Nodes to be explored. - private final PriorityQueue _opened; - // Container holding Nodes already explored. - private final List _closed; - - // Target coordinates. - private int _gtx; - private int _gty; - private int _gtz; - - private Node _current; - - /** - * Constructor of NodeBuffer. - * @param size : The total size buffer. Determines the amount of {@link Node}s to be used for pathfinding. - */ - public NodeBuffer(int size) - { - // Create buffers based on given size. - _buffer = new Node[size]; - _opened = new PriorityQueue<>(size); - _closed = new ArrayList<>(size); - - // Create Nodes. - for (int i = 0; i < size; i++) - { - _buffer[i] = new Node(); - } - } - - /** - * Find path consisting of Nodes. Starts at origin coordinates, ends in target coordinates. - * @param gox : origin point x - * @param goy : origin point y - * @param goz : origin point z - * @param gtx : target point x - * @param gty : target point y - * @param gtz : target point z - * @return The list of {@link Location} for the path. Empty, if path not found. - */ - public List findPath(int gox, int goy, int goz, int gtx, int gty, int gtz) - { - // Set target coordinates. - _gtx = gtx; - _gty = gty; - _gtz = gtz; - - // Get node from buffer. - _current = _buffer[_bufferIndex++]; - - // Set node geodata coordinates and movement cost. - _current.setGeo(gox, goy, goz, GeoEngine.getInstance().getNsweNearest(gox, goy, goz), GeoStructure.CELL_EXPANSION_ALL); - _current.setCost(null, 0, getCostH(gox, goy, goz)); - - int count = 0; - do - { - // Move node to closed list. - _closed.add(_current); - - // Target reached, calculate path and return. - if ((_current.getGeoX() == _gtx) && (_current.getGeoY() == _gty) && (_current.getZ() == _gtz)) - { - return constructPath(); - } - - // Expand current node. - expand(); - - // Get next node to expand. - _current = _opened.poll(); - } - while ((_current != null) && (_bufferIndex < _buffer.length) && (++count < Config.MAX_ITERATIONS)); - - // Iteration failed, return empty path. - return Collections.emptyList(); - } - - /** - * Build the path from subsequent nodes. Skip nodes in straight directions, keep only corner nodes. - * @return List of {@link Node}s representing the path. - */ - private List constructPath() - { - // Create result. - final LinkedList path = new LinkedList<>(); - - // Clear X/Y direction. - int dx = 0; - int dy = 0; - - // Get parent node. - Node parent = _current.getParent(); - - // While parent exists. - while (parent != null) - { - // Get parent node to current node X/Y direction. - final int nx = parent.getGeoX() - _current.getGeoX(); - final int ny = parent.getGeoY() - _current.getGeoY(); - - // Direction has changed? - if ((dx != nx) || (dy != ny)) - { - // Add current node to the beginning of the path (Node must be cloned, as NodeBuffer reuses them). - path.addFirst(_current.clone()); - - // Update X/Y direction. - dx = nx; - dy = ny; - } - - // Move current node and update its parent. - _current = parent; - parent = _current.getParent(); - } - - return path; - } - - public boolean isLocked() - { - return _lock.tryLock(); - } - - public void free() - { - _opened.clear(); - _closed.clear(); - - for (int i = 0; i < (_bufferIndex - 1); i++) - { - _buffer[i].clean(); - } - _bufferIndex = 0; - - _current = null; - - _lock.unlock(); - } - - /** - * Expand the current {@link Node} by exploring its neighbors (axially and diagonally). - */ - private void expand() - { - // Movement is blocked, skip. - final byte nswe = _current.getNSWE(); - byte expand = _current.getNsweExpand(); - if ((nswe & expand) == GeoStructure.CELL_FLAG_NONE) - { - return; - } - - // Get geo coordinates of the node to be expanded. - // Note: Z coord shifted up to avoid dual-layer issues. - final int x = _current.getGeoX(); - final int y = _current.getGeoY(); - final int z = _current.getZ() + GeoStructure.CELL_IGNORE_HEIGHT; - - byte nsweN = GeoStructure.CELL_FLAG_NONE; - byte nsweS = GeoStructure.CELL_FLAG_NONE; - byte nsweW = GeoStructure.CELL_FLAG_NONE; - byte nsweE = GeoStructure.CELL_FLAG_NONE; - - switch (expand) - { - case GeoStructure.CELL_EXPANSION_N: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_N) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_S: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - - if (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - if ((getNodeNswe(x - 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - if ((getNodeNswe(x + 1, y, z) & GeoStructure.CELL_FLAG_S) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_W: - { - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_W) != 0) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_E: - { - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - - if (((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0)) - { - if ((getNodeNswe(x, y - 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - } - - if (((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0)) - { - if ((getNodeNswe(x, y + 1, z) & GeoStructure.CELL_FLAG_E) != 0) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - } - } - break; - } - case GeoStructure.CELL_EXPANSION_NW: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_NE: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SW: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_SE: - { - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - case GeoStructure.CELL_EXPANSION_ALL: - { - if ((nswe & GeoStructure.CELL_FLAG_N) != 0) - { - nsweN = addNode(x, y - 1, z, GeoStructure.CELL_EXPANSION_N, false); - } - if ((nswe & GeoStructure.CELL_FLAG_S) != 0) - { - nsweS = addNode(x, y + 1, z, GeoStructure.CELL_EXPANSION_S, false); - } - if ((nswe & GeoStructure.CELL_FLAG_W) != 0) - { - nsweW = addNode(x - 1, y, z, GeoStructure.CELL_EXPANSION_W, false); - } - if ((nswe & GeoStructure.CELL_FLAG_E) != 0) - { - nsweE = addNode(x + 1, y, z, GeoStructure.CELL_EXPANSION_E, false); - } - - if (((nsweW & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y - 1, z, GeoStructure.CELL_EXPANSION_NW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y - 1, z, GeoStructure.CELL_EXPANSION_NE, true); - } - if (((nsweW & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) - { - addNode(x - 1, y + 1, z, GeoStructure.CELL_EXPANSION_SW, true); - } - if (((nsweE & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) - { - addNode(x + 1, y + 1, z, GeoStructure.CELL_EXPANSION_SE, true); - } - break; - } - } - } - - private static byte getNodeNswe(int gx, int gy, int gz) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gz); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata nswe. - return block.getNswe(index); - } - - /** - * Take {@link Node} from buffer, validate it and add to opened list. - * @param gx : The new node X geodata coordinate. - * @param gy : The new node Y geodata coordinate. - * @param gzValue : The new node Z geodata coordinate. - * @param nsweExpand : The allowed directions to be expanded on the new node. - * @param diagonal : The new node is being explored in diagonal direction. - * @return The nswe of the added node. Blank, if not added. - */ - private byte addNode(int gx, int gy, int gzValue, byte nsweExpand, boolean diagonal) - { - // Check new node is out of geodata grid (world coordinates). - if ((gx < 0) || (gx >= GeoStructure.GEO_CELLS_X) || (gy < 0) || (gy >= GeoStructure.GEO_CELLS_Y)) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Check buffer has reached capacity. - if (_bufferIndex >= _buffer.length) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get geodata block and check if there is a layer at given coordinates. - final ABlock block = GeoEngine.getInstance().getBlock(gx, gy); - final int index = block.getIndexBelow(gx, gy, gzValue); - if (index < 0) - { - return GeoStructure.CELL_FLAG_NONE; - } - - // Get node geodata Z and nswe. - final int gz = block.getHeight(index); - final byte nswe = block.getNswe(index); - - // Get node from current index (don't move index yet). - Node node = _buffer[_bufferIndex]; - - // Calculate node weight. - int weight; - if (nswe == GeoStructure.CELL_FLAG_ALL) - { - weight = diagonal ? Config.MOVE_WEIGHT_DIAG : Config.MOVE_WEIGHT; - } - else - { - weight = diagonal ? Config.OBSTACLE_WEIGHT_DIAG : Config.OBSTACLE_WEIGHT; - } - - // Set node geodata coordinates. - node.setGeo(gx, gy, gz, nswe, nsweExpand); - - // Node is already added to opened list, return. - if (_opened.contains(node)) - { - return nswe; - } - - // Node was already expanded, return. - if (_closed.contains(node)) - { - return nswe; - } - - // The node is to be used. Set node movement cost and add it to opened list. Move the buffer index. - node.setCost(_current, weight, getCostH(gx, gy, gz)); - _opened.add(node); - _bufferIndex++; - return nswe; - } - - /** - * Calculate cost H value, calculated using diagonal distance method.
- * Note: Manhattan distance is too simple, causing to explore more unwanted cells. - * @param gx : The node geodata X coordinate. - * @param gy : The node geodata Y coordinate. - * @param gz : The node geodata Z coordinate. - * @return The cost H value (estimated cost to reach the target). - */ - private int getCostH(int gx, int gy, int gz) - { - // Get differences to the target. - int dx = Math.abs(gx - _gtx); - int dy = Math.abs(gy - _gty); - int dz = Math.abs(gz - _gtz) / GeoStructure.CELL_HEIGHT; - - // Get diagonal and axial differences to the target. - final int ds = Math.min(dx, Math.min(dy, dz)); - dx -= ds; - dy -= ds; - dz -= ds; - int dd; - int d; - if (dx == 0) - { - dd = Math.min(dy, dz); - d = Math.max(dy, dz) - dd; - } - else if (dy == 0) - { - dd = Math.min(dx, dz); - d = Math.max(dx, dz) - dd; - } - else - { - dd = Math.min(dx, dy); - d = Math.max(dx, dy) - dd; - } - - // Calculate the diagonal distance of the node to the target. - return (int) (((int) (ds * Math.sqrt(3)) + (dd * Config.HEURISTIC_WEIGHT_DIAG) + (d * Config.HEURISTIC_WEIGHT)) * 1.2); - } -} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000000..6e4bb5ce46 --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,100 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.List; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes.CellPathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoPathFinding; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public abstract class PathFinding +{ + public static PathFinding getInstance() + { + return Config.PATHFINDING == 1 ? GeoPathFinding.getInstance() : CellPathFinding.getInstance(); + } + + public abstract boolean pathNodesExist(short regionoffset); + + public abstract List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable); + + /** + * Convert geodata position to pathnode position + * @param geoPos + * @return pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); // OK? + } + + /** + * Convert node position to pathnode block position + * @param nodePos + * @return pathnode block position (0...255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Convert pathnode x to World x position + * @param nodeX rx + * @return + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Convert pathnode y to World y position + * @param nodeY + * @return + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + public String[] getStat() + { + return null; + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java new file mode 100644 index 0000000000..9ae39a40e0 --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNode.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +public class CellNode extends AbstractNode +{ + private CellNode _next = null; + private boolean _isInUse = true; + private float _cost = -1000; + + public CellNode(NodeLoc loc) + { + super(loc); + } + + public boolean isInUse() + { + return _isInUse; + } + + public void setInUse() + { + _isInUse = true; + } + + public CellNode getNext() + { + return _next; + } + + public void setNext(CellNode next) + { + _next = next; + } + + public float getCost() + { + return _cost; + } + + public void setCost(double cost) + { + _cost = (float) cost; + } + + public void free() + { + setParent(null); + _cost = -1000; + _isInUse = false; + _next = null; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java new file mode 100644 index 0000000000..c1bbda5cc8 --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellNodeBuffer.java @@ -0,0 +1,335 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.l2jmobius.Config; + +/** + * @author DS Credits to Diamond + */ +public class CellNodeBuffer +{ + private static final int MAX_ITERATIONS = 3500; + + private final ReentrantLock _lock = new ReentrantLock(); + private final int _mapSize; + private final CellNode[][] _buffer; + + private int _baseX = 0; + private int _baseY = 0; + + private int _targetX = 0; + private int _targetY = 0; + private int _targetZ = 0; + + private long _timeStamp = 0; + private long _lastElapsedTime = 0; + + private CellNode _current = null; + + public CellNodeBuffer(int size) + { + _mapSize = size; + _buffer = new CellNode[_mapSize][_mapSize]; + } + + public final boolean lock() + { + return _lock.tryLock(); + } + + public final CellNode findPath(int x, int y, int z, int tx, int ty, int tz) + { + _timeStamp = System.currentTimeMillis(); + _baseX = x + ((tx - x - _mapSize) / 2); // Middle of the line (x,y) - (tx,ty). + _baseY = y + ((ty - y - _mapSize) / 2); // Will be in the center of the buffer. + _targetX = tx; + _targetY = ty; + _targetZ = tz; + _current = getNode(x, y, z); + _current.setCost(getCost(x, y, z, Config.HIGH_WEIGHT)); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if ((_current.getLoc().getNodeX() == _targetX) && (_current.getLoc().getNodeY() == _targetY) && (Math.abs(_current.getLoc().getZ() - _targetZ) < 64)) + { + return _current; // Found. + } + + getNeighbors(); + if (_current.getNext() == null) + { + return null; // No more ways. + } + + _current = _current.getNext(); + } + return null; + } + + public final void free() + { + _current = null; + + CellNode node; + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + node = _buffer[i][j]; + if (node != null) + { + node.free(); + } + } + } + + _lock.unlock(); + _lastElapsedTime = System.currentTimeMillis() - _timeStamp; + } + + public final long getElapsedTime() + { + return _lastElapsedTime; + } + + public final List debugPath() + { + final List result = new LinkedList<>(); + for (CellNode n = _current; n.getParent() != null; n = (CellNode) n.getParent()) + { + result.add(n); + n.setCost(-n.getCost()); + } + + for (int i = 0; i < _mapSize; i++) + { + for (int j = 0; j < _mapSize; j++) + { + final CellNode n = _buffer[i][j]; + if ((n == null) || !n.isInUse() || (n.getCost() <= 0)) + { + continue; + } + + result.add(n); + } + } + return result; + } + + private final void getNeighbors() + { + if (_current.getLoc().canGoNone()) + { + return; + } + + final int x = _current.getLoc().getNodeX(); + final int y = _current.getLoc().getNodeY(); + final int z = _current.getLoc().getZ(); + + CellNode nodeE = null; + CellNode nodeS = null; + CellNode nodeW = null; + CellNode nodeN = null; + + // East + if (_current.getLoc().canGoEast()) + { + nodeE = addNode(x + 1, y, z, false); + } + + // South + if (_current.getLoc().canGoSouth()) + { + nodeS = addNode(x, y + 1, z, false); + } + + // West + if (_current.getLoc().canGoWest()) + { + nodeW = addNode(x - 1, y, z, false); + } + + // North + if (_current.getLoc().canGoNorth()) + { + nodeN = addNode(x, y - 1, z, false); + } + + if (!Config.ADVANCED_DIAGONAL_STRATEGY) + { + return; + } + + // SouthEast + if ((nodeE != null) && (nodeS != null) && nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) + { + addNode(x + 1, y + 1, z, true); + } + + // SouthWest + if ((nodeS != null) && (nodeW != null) && nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) + { + addNode(x - 1, y + 1, z, true); + } + + // NorthEast + if ((nodeN != null) && (nodeE != null) && nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) + { + addNode(x + 1, y - 1, z, true); + } + + // NorthWest + if ((nodeN != null) && (nodeW != null) && nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) + { + addNode(x - 1, y - 1, z, true); + } + } + + private final CellNode getNode(int x, int y, int z) + { + final int aX = x - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return null; + } + + final int aY = y - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return null; + } + + CellNode result = _buffer[aX][aY]; + if (result == null) + { + result = new CellNode(new NodeLoc(x, y, z)); + _buffer[aX][aY] = result; + } + else if (!result.isInUse()) + { + result.setInUse(); + // Re-init node if needed. + if (result.getLoc() != null) + { + result.getLoc().set(x, y, z); + } + else + { + result.setLoc(new NodeLoc(x, y, z)); + } + } + + return result; + } + + private final CellNode addNode(int x, int y, int z, boolean diagonal) + { + final CellNode newNode = getNode(x, y, z); + if (newNode == null) + { + return null; + } + if (newNode.getCost() >= 0) + { + return newNode; + } + + final int geoZ = newNode.getLoc().getZ(); + + final int stepZ = Math.abs(geoZ - _current.getLoc().getZ()); + float weight = diagonal ? Config.DIAGONAL_WEIGHT : Config.LOW_WEIGHT; + + if (!newNode.getLoc().canGoAll() || (stepZ > 16)) + { + weight = Config.HIGH_WEIGHT; + } + else if (isHighWeight(x + 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x - 1, y, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y + 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + else if (isHighWeight(x, y - 1, geoZ)) + { + weight = Config.MEDIUM_WEIGHT; + } + + newNode.setParent(_current); + newNode.setCost(getCost(x, y, geoZ, weight)); + + CellNode node = _current; + int count = 0; + while ((node.getNext() != null) && (count < (MAX_ITERATIONS * 4))) + { + count++; + if (node.getNext().getCost() > newNode.getCost()) + { + // Insert node into a chain. + newNode.setNext(node.getNext()); + break; + } + node = node.getNext(); + } + if (count == (MAX_ITERATIONS * 4)) + { + System.err.println("Pathfinding: too long loop detected, cost:" + newNode.getCost()); + } + + node.setNext(newNode); // Add last. + + return newNode; + } + + private final boolean isHighWeight(int x, int y, int z) + { + final CellNode result = getNode(x, y, z); + return (result == null) || !result.getLoc().canGoAll() || (Math.abs(result.getLoc().getZ() - z) > 16); + } + + private final double getCost(int x, int y, int z, float weight) + { + final int dX = x - _targetX; + final int dY = y - _targetY; + final int dZ = z - _targetZ; + // Math.abs(dx) + Math.abs(dy) + Math.abs(dz) / 16 + double result = Math.sqrt((dX * dX) + (dY * dY) + ((dZ * dZ) / 256.0)); + if (result > weight) + { + result += weight; + } + + if (result > Float.MAX_VALUE) + { + result = Float.MAX_VALUE; + } + + return result; + } +} \ No newline at end of file diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java new file mode 100644 index 0000000000..d1921460bf --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/CellPathFinding.java @@ -0,0 +1,403 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.instancemanager.IdManager; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.item.instance.Item; + +/** + * @author Sami, DS Credits to Diamond + */ +public class CellPathFinding extends PathFinding +{ + private static final Logger LOGGER = 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 List _debugItems = null; + + protected CellPathFinding() + { + try + { + final 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) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + @Override + public boolean pathNodesExist(short regionoffset) + { + return false; + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); + if (!GeoEngine.getInstance().hasGeo(x, y)) + { + return null; + } + final int gz = GeoEngine.getInstance().getHeight(x, y, z); + final int gtx = GeoEngine.getInstance().getGeoX(tx); + final int gty = GeoEngine.getInstance().getGeoY(ty); + if (!GeoEngine.getInstance().hasGeo(tx, ty)) + { + return null; + } + final int gtz = GeoEngine.getInstance().getHeight(tx, ty, tz); + final CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable); + if (buffer == null) + { + return null; + } + + final boolean debug = Config.DEBUG_PATH && playable; + + if (debug) + { + if (_debugItems == null) + { + _debugItems = new CopyOnWriteArrayList<>(); + } + else + { + for (Item item : _debugItems) + { + item.decayMe(); + } + + _debugItems.clear(); + } + } + + List path = null; + try + { + final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + + if (debug) + { + for (CellNode n : buffer.debugPath()) + { + if (n.getCost() < 0) + { + 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) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + finally + { + buffer.free(); + } + + if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0)) + { + _findSuccess++; + return path; + } + + final long timeStamp = System.currentTimeMillis(); + _postFilterUses++; + if (playable) + { + _postFilterPlayableUses++; + } + + boolean remove; + int pass = 0; + do + { + pass++; + _postFilterPasses++; + + remove = false; + final Iterator endPoint = path.iterator(); + endPoint.next(); + int currentX = x; + int currentY = y; + int currentZ = z; + + int midPoint = 0; + while (endPoint.hasNext()) + { + final AbstractNodeLoc locMiddle = path.get(midPoint); + final AbstractNodeLoc locEnd = endPoint.next(); + if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) + { + path.remove(midPoint); + remove = true; + if (debug) + { + dropDebugItem(735, 1, locMiddle); + } + } + else + { + currentX = locMiddle.getX(); + currentY = locMiddle.getY(); + currentZ = locMiddle.getZ(); + midPoint++; + } + } + } + // Only one postfilter pass for AI. + while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES)); + + if (debug) + { + path.forEach(n -> dropDebugItem(65, 1, n)); + } + + _findSuccess++; + _postFilterElapsed += System.currentTimeMillis() - timeStamp; + return path; + } + + private List constructPath(AbstractNode node) + { + final List path = new CopyOnWriteArrayList<>(); + int previousDirectionX = Integer.MIN_VALUE; + int previousDirectionY = Integer.MIN_VALUE; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + if (!Config.ADVANCED_DIAGONAL_STRATEGY && (tempNode.getParent().getParent() != null)) + { + final int tmpX = tempNode.getLoc().getNodeX() - tempNode.getParent().getParent().getLoc().getNodeX(); + final int tmpY = tempNode.getLoc().getNodeY() - tempNode.getParent().getParent().getLoc().getNodeY(); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + directionX = tmpX; + directionY = tmpY; + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + } + else + { + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + } + + // Only add a new route point if moving direction changes. + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + + path.add(0, tempNode.getLoc()); + tempNode.setLoc(null); + } + + tempNode = tempNode.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 Item item = new Item(IdManager.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; + List 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), Config.EOL); + } + StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); + result[result.length - 1] = stat.toString(); + + return result; + } + + public static CellPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CellPathFinding INSTANCE = new CellPathFinding(); + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java new file mode 100644 index 0000000000..18a199c69c --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/cellnodes/NodeLoc.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.cellnodes; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + +/** + * @author -Nemesiss-, HorridoJoho + */ +public class NodeLoc extends AbstractNodeLoc +{ + private int _x; + private int _y; + private boolean _goNorth; + private boolean _goEast; + private boolean _goSouth; + private boolean _goWest; + private int _geoHeight; + + public NodeLoc(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _goNorth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_NORTH); + _goEast = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_EAST); + _goSouth = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_SOUTH); + _goWest = GeoEngine.getInstance().checkNearestNswe(x, y, z, Cell.NSWE_WEST); + _geoHeight = GeoEngine.getInstance().getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return _goNorth; + } + + public boolean canGoEast() + { + return _goEast; + } + + public boolean canGoSouth() + { + return _goSouth; + } + + public boolean canGoWest() + { + return _goWest; + } + + public boolean canGoNone() + { + return !canGoNorth() && !canGoEast() && !canGoSouth() && !canGoWest(); + } + + public boolean canGoAll() + { + return canGoNorth() && canGoEast() && canGoSouth() && canGoWest(); + } + + @Override + public int getX() + { + return GeoEngine.getInstance().getWorldX(_x); + } + + @Override + public int getY() + { + return GeoEngine.getInstance().getWorldY(_y); + } + + @Override + public int getZ() + { + return _geoHeight; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + int nswe = 0; + if (canGoNorth()) + { + nswe |= Cell.NSWE_NORTH; + } + if (canGoEast()) + { + nswe |= Cell.NSWE_EAST; + } + if (canGoSouth()) + { + nswe |= Cell.NSWE_SOUTH; + } + if (canGoWest()) + { + nswe |= Cell.NSWE_WEST; + } + + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | nswe); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NodeLoc)) + { + return false; + } + final NodeLoc other = (NodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (!_goNorth == !other._goNorth) && (!_goEast == !other._goEast) && (!_goSouth == !other._goSouth) && (!_goWest == !other._goWest) && (_geoHeight == other._geoHeight); + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java new file mode 100644 index 0000000000..29951bb62f --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNode.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class GeoNode extends AbstractNode +{ + private final int _neighborsIdx; + private short _cost; + private GeoNode[] _neighbors; + + public GeoNode(GeoNodeLoc loc, int neighborsIdx) + { + super(loc); + _neighborsIdx = neighborsIdx; + } + + public short getCost() + { + return _cost; + } + + public void setCost(int cost) + { + _cost = (short) cost; + } + + public GeoNode[] getNeighbors() + { + return _neighbors; + } + + public void attachNeighbors(GeoNode[] neighbors) + { + _neighbors = neighbors; + } + + public int getNeighborsIdx() + { + return _neighborsIdx; + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java new file mode 100644 index 0000000000..e3d35e8c30 --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoNodeLoc.java @@ -0,0 +1,102 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.model.World; + +/** + * @author -Nemesiss- + */ +public class GeoNodeLoc extends AbstractNodeLoc +{ + private final short _x; + private final short _y; + private final short _z; + + public GeoNodeLoc(short x, short y, short z) + { + _x = x; + _y = y; + _z = z; + } + + @Override + public int getX() + { + return World.WORLD_X_MIN + (_x * 128) + 48; + } + + @Override + public int getY() + { + return World.WORLD_Y_MIN + (_y * 128) + 48; + } + + @Override + public int getZ() + { + return _z; + } + + @Override + public void setZ(short z) + { + } + + @Override + public int getNodeX() + { + return _x; + } + + @Override + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof GeoNodeLoc)) + { + return false; + } + final GeoNodeLoc other = (GeoNodeLoc) obj; + return (_x == other._x) && (_y == other._y) && (_z == other._z); + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java new file mode 100644 index 0000000000..6963be3691 --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/geonodes/GeoPathFinding.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.geonodes; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.geoengine.pathfinding.utils.FastNodeList; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.World; +import org.l2jmobius.gameserver.model.instancezone.Instance; + +/** + * @author -Nemesiss- + */ +public class GeoPathFinding extends PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(GeoPathFinding.class.getName()); + + private static final Map PATH_NODES = new HashMap<>(); + private static final Map PATH_NODE_INDEX = new HashMap<>(); + + @Override + public boolean pathNodesExist(short regionoffset) + { + return PATH_NODE_INDEX.containsKey(regionoffset); + } + + @Override + public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance, boolean playable) + { + final int gx = (x - World.WORLD_X_MIN) >> 4; + final int gy = (y - World.WORLD_Y_MIN) >> 4; + final short gz = (short) z; + final int gtx = (tx - World.WORLD_X_MIN) >> 4; + final int gty = (ty - World.WORLD_Y_MIN) >> 4; + final short gtz = (short) tz; + + final GeoNode start = readNode(gx, gy, gz); + final 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 = GeoEngine.getInstance().getValidLocation(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instance); + 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 = GeoEngine.getInstance().getValidLocation(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instance); + if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY())) + { + return null; // Cannot reach closest... + } + + return searchByClosest2(start, end); + } + + public List 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. + final FastNodeList visited = new FastNodeList(550); + + // List of Nodes to Visit. + final LinkedList toVisit = new LinkedList<>(); + toVisit.add(start); + final int targetX = end.getLoc().getNodeX(); + final int targetY = end.getLoc().getNodeY(); + + int dx, dy; + boolean added; + int i = 0; + while (i < 550) + { + GeoNode node; + try + { + node = toVisit.removeFirst(); + } + catch (Exception e) + { + // No Path found + return null; + } + if (node.equals(end)) + { + return constructPath2(node); + } + + i++; + visited.add(node); + node.attachNeighbors(readNeighbors(node)); + final GeoNode[] neighbors = node.getNeighbors(); + if (neighbors == null) + { + continue; + } + for (GeoNode n : neighbors) + { + if (!visited.containsRev(n) && !toVisit.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 < toVisit.size(); index++) + { + // Supposed to find it quite early. + if (toVisit.get(index).getCost() > n.getCost()) + { + toVisit.add(index, n); + added = true; + break; + } + } + if (!added) + { + toVisit.addLast(n); + } + } + } + } + // No Path found. + return null; + } + + public List constructPath2(AbstractNode node) + { + final LinkedList path = new LinkedList<>(); + int previousDirectionX = -1000; + int previousDirectionY = -1000; + int directionX; + int directionY; + + AbstractNode tempNode = node; + while (tempNode.getParent() != null) + { + // Only add a new route point if moving direction changes. + directionX = tempNode.getLoc().getNodeX() - tempNode.getParent().getLoc().getNodeX(); + directionY = tempNode.getLoc().getNodeY() - tempNode.getParent().getLoc().getNodeY(); + + if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) + { + previousDirectionX = directionX; + previousDirectionY = directionY; + path.addFirst(tempNode.getLoc()); + } + tempNode = tempNode.getParent(); + } + return path; + } + + private GeoNode[] readNeighbors(GeoNode n) + { + if (n.getLoc() == null) + { + return null; + } + + int idx = n.getNeighborsIdx(); + + final int nodeX = n.getLoc().getNodeX(); + final int nodeY = n.getLoc().getNodeY(); + + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + final ByteBuffer pn = PATH_NODES.get(regoffset); + + final List> neighbors = new ArrayList<>(8); + GeoNode newNode; + short newNodeX; + short newNodeY; + + // Region for sure will change, we must read from correct file + byte neighbor = pn.get(idx++); // N + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // E + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SE + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX + 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // S + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) nodeX; + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // SW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY + 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // W + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) nodeY; + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + neighbor = pn.get(idx++); // NW + if (neighbor > 0) + { + neighbor--; + newNodeX = (short) (nodeX - 1); + newNodeY = (short) (nodeY - 1); + newNode = readNode(newNodeX, newNodeY, neighbor); + if (newNode != null) + { + neighbors.add(newNode); + } + } + return neighbors.toArray(new GeoNode[neighbors.size()]); + } + + // Private + + private GeoNode readNode(short nodeX, short nodeY, byte layer) + { + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + final byte nodes = pn.get(idx); + idx += (layer * 10) + 1; // byte + layer*10byte + if (nodes < layer) + { + LOGGER.warning("SmthWrong!"); + } + final short node_z = pn.getShort(idx); + idx += 2; + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, node_z), idx); + } + + private GeoNode readNode(int gx, int gy, short z) + { + final short nodeX = getNodePos(gx); + final short nodeY = getNodePos(gy); + final short regoffset = getRegionOffset(getRegionX(nodeX), getRegionY(nodeY)); + if (!pathNodesExist(regoffset)) + { + return null; + } + final short nbx = getNodeBlock(nodeX); + final short nby = getNodeBlock(nodeY); + int idx = PATH_NODE_INDEX.get(regoffset).get((nby << 8) + nbx); + final ByteBuffer pn = PATH_NODES.get(regoffset); + // Reading. + byte nodes = pn.get(idx++); + int idx2 = 0; // Create index to nearlest node by z. + short lastZ = Short.MIN_VALUE; + while (nodes > 0) + { + final short node_z = pn.getShort(idx); + if (Math.abs(lastZ - z) > Math.abs(node_z - z)) + { + lastZ = node_z; + idx2 = idx + 2; + } + idx += 10; // short + 8 byte + nodes--; + } + return new GeoNode(new GeoNodeLoc(nodeX, nodeY, lastZ), idx2); + } + + protected GeoPathFinding() + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + loadPathNodeFile((byte) regionX, (byte) regionY); + } + } + } + + private void loadPathNodeFile(byte rx, byte ry) + { + final short regionoffset = getRegionOffset(rx, ry); + final File file = new File(Config.PATHNODE_PATH.toString(), rx + "_" + ry + ".pn"); + if (!file.exists()) + { + return; + } + + // LOGGER.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry); + + int node = 0; + int size = 0; + int index = 0; + + // Create a read-only memory-mapped file. + try (RandomAccessFile raf = new RandomAccessFile(file, "r"); + FileChannel roChannel = raf.getChannel()) + { + size = (int) roChannel.size(); + final MappedByteBuffer nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + + // Indexing pathnode files, so we will know where each block starts. + final IntBuffer indexs = IntBuffer.allocate(65536); + + while (node < 65536) + { + final byte layer = nodes.get(index); + indexs.put(node++, index); + index += (layer * 10) + 1; + } + PATH_NODE_INDEX.put(regionoffset, indexs); + PATH_NODES.put(regionoffset, nodes); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e); + } + } + + public static GeoPathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoPathFinding INSTANCE = new GeoPathFinding(); + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java new file mode 100644 index 0000000000..8b8e92e4df --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/BinaryNodeHeap.java @@ -0,0 +1,124 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import org.l2jmobius.gameserver.geoengine.pathfinding.geonodes.GeoNode; + +/** + * @author -Nemesiss- + */ +public class BinaryNodeHeap +{ + private final GeoNode[] _list; + private int _size; + + public BinaryNodeHeap(int size) + { + _list = new GeoNode[size + 1]; + _size = 0; + } + + public void add(GeoNode n) + { + _size++; + int pos = _size; + _list[pos] = n; + while (pos != 1) + { + final int p2 = pos / 2; + if (_list[pos].getCost() <= _list[p2].getCost()) + { + final GeoNode temp = _list[p2]; + _list[p2] = _list[pos]; + _list[pos] = temp; + pos = p2; + } + else + { + break; + } + } + } + + public GeoNode removeFirst() + { + final GeoNode first = _list[1]; + _list[1] = _list[_size]; + _list[_size] = null; + _size--; + int pos = 1; + int cpos; + int dblcpos; + GeoNode temp; + while (true) + { + cpos = pos; + dblcpos = cpos * 2; + if ((dblcpos + 1) <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + if (_list[pos].getCost() >= _list[dblcpos + 1].getCost()) + { + pos = dblcpos + 1; + } + } + else if (dblcpos <= _size) + { + if (_list[cpos].getCost() >= _list[dblcpos].getCost()) + { + pos = dblcpos; + } + } + + if (cpos != pos) + { + temp = _list[cpos]; + _list[cpos] = _list[pos]; + _list[pos] = temp; + } + else + { + break; + } + } + return first; + } + + public boolean contains(GeoNode n) + { + if (_size == 0) + { + return false; + } + for (int i = 1; i <= _size; i++) + { + if (_list[i].equals(n)) + { + return true; + } + } + return false; + } + + public boolean isEmpty() + { + return _size == 0; + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java new file mode 100644 index 0000000000..15f975b6d4 --- /dev/null +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/geoengine/pathfinding/utils/FastNodeList.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * 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 org.l2jmobius.gameserver.geoengine.pathfinding.utils; + +import java.util.ArrayList; + +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; + +/** + * @author -Nemesiss- + */ +public class FastNodeList +{ + private final ArrayList> _list; + + public FastNodeList(int size) + { + _list = new ArrayList<>(size); + } + + public void add(AbstractNode n) + { + _list.add(n); + } + + public boolean contains(AbstractNode n) + { + return _list.contains(n); + } + + public boolean containsRev(AbstractNode n) + { + return _list.lastIndexOf(n) != -1; + } +} diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/model/Spawn.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/model/Spawn.java index ef77fde36b..da50a0b5fe 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/model/Spawn.java +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/model/Spawn.java @@ -400,7 +400,7 @@ public class Spawn extends Location implements IIdentifiable, INamable final int randX = newlocx + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); final int randY = newlocy + Rnd.get(Config.MOB_MIN_SPAWN_RANGE, Config.MOB_MAX_SPAWN_RANGE); if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld()) // - && GeoEngine.getInstance().canSee(newlocx, newlocy, newlocz, npc.getCollisionHeight(), randX, randY, newlocz, 0)) + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, npc.getInstanceWorld())) { newlocx = randX; newlocy = randY; diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/model/actor/Creature.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/model/actor/Creature.java index 4bb988186a..3db7900695 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/model/actor/Creature.java +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/model/actor/Creature.java @@ -67,6 +67,8 @@ import org.l2jmobius.gameserver.enums.Team; import org.l2jmobius.gameserver.enums.TeleportWhereType; import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; import org.l2jmobius.gameserver.instancemanager.IdManager; import org.l2jmobius.gameserver.instancemanager.MapRegionManager; import org.l2jmobius.gameserver.instancemanager.QuestManager; @@ -821,7 +823,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe { int x = xValue; int y = yValue; - int z = zValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); int heading = headingValue; Instance instance = instanceValue; @@ -2698,7 +2700,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe public boolean disregardingGeodata; public int onGeodataPathIndex; - public List geoPath; + public List geoPath; public int geoPathAccurateTx; public int geoPathAccurateTy; public int geoPathGtx; @@ -3486,7 +3488,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // Movement checks. - if (Config.PATHFINDING && !(this instanceof FriendlyNpc)) + if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc)) { final double originalDistance = distance; final int originalX = x; @@ -3530,7 +3532,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) { // Path calculation -- overrides previous movement check - m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer()); if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found { if (isPlayer() && !_isFlying && !isInWater) @@ -3574,7 +3576,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe } // If no distance to go through, the movement is canceled - if ((distance < 1) && (Config.PATHFINDING || isPlayable())) + if ((distance < 1) && ((Config.PATHFINDING > 0) || isPlayable())) { if (isSummon()) { diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/util/GeoUtils.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/util/GeoUtils.java index 733cce0480..dbbc2044bc 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/util/GeoUtils.java +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/util/GeoUtils.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.util; import java.awt.Color; import org.l2jmobius.gameserver.geoengine.GeoEngine; -import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; @@ -30,21 +30,21 @@ public final class GeoUtils { public static void debug2DLine(Player player, int x, int y, int tx, int ty, int z) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); while (iter.next()) { - final int wx = GeoEngine.getWorldX(iter.x()); - final int wy = GeoEngine.getWorldY(iter.y()); + final int wx = GeoEngine.getInstance().getWorldX(iter.x()); + final int wy = GeoEngine.getInstance().getWorldY(iter.y()); prim.addPoint(Color.RED, wx, wy, z); } @@ -53,21 +53,21 @@ public final class GeoUtils public static void debug3DLine(Player player, int x, int y, int z, int tx, int ty, int tz) { - final int gx = GeoEngine.getGeoX(x); - final int gy = GeoEngine.getGeoY(y); + final int gx = GeoEngine.getInstance().getGeoX(x); + final int gy = GeoEngine.getInstance().getGeoY(y); - final int tgx = GeoEngine.getGeoX(tx); - final int tgy = GeoEngine.getGeoY(ty); + final int tgx = GeoEngine.getInstance().getGeoX(tx); + final int tgy = GeoEngine.getInstance().getGeoY(ty); final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); - prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), tz); + prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz); iter.next(); int prevX = iter.x(); int prevY = iter.y(); - int wx = GeoEngine.getWorldX(prevX); - int wy = GeoEngine.getWorldY(prevY); + int wx = GeoEngine.getInstance().getWorldX(prevX); + int wy = GeoEngine.getInstance().getWorldY(prevY); int wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -78,8 +78,8 @@ public final class GeoUtils if ((curX != prevX) || (curY != prevY)) { - wx = GeoEngine.getWorldX(curX); - wy = GeoEngine.getWorldY(curY); + wx = GeoEngine.getInstance().getWorldX(curX); + wy = GeoEngine.getInstance().getWorldY(curY); wz = iter.z(); prim.addPoint(Color.RED, wx, wy, wz); @@ -93,7 +93,7 @@ public final class GeoUtils private static Color getDirectionColor(int x, int y, int z, int nswe) { - if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) != 0) + if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) { return Color.GREEN; } @@ -109,8 +109,9 @@ public final class GeoUtils int iPacket = 0; ExServerPrimitive exsp = null; - final int playerGx = GeoEngine.getGeoX(player.getX()); - final int playerGy = GeoEngine.getGeoY(player.getY()); + final GeoEngine ge = GeoEngine.getInstance(); + final int playerGx = ge.getGeoX(player.getX()); + final int playerGy = ge.getGeoY(player.getY()); for (int dx = -geoRadius; dx <= geoRadius; ++dx) { for (int dy = -geoRadius; dy <= geoRadius; ++dy) @@ -134,32 +135,32 @@ public final class GeoUtils final int gx = playerGx + dx; final int gy = playerGy + dy; - final int x = GeoEngine.getWorldX(gx); - final int y = GeoEngine.getWorldY(gy); - final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + final int x = ge.getWorldX(gx); + final int y = ge.getWorldY(gy); + final int z = ge.getNearestZ(gx, gy, player.getZ()); // north arrow - Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z); exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z); exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z); exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); // east arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z); exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z); exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z); exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); // south arrow - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z); exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z); exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z); exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z); - col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST); exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z); exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z); exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z); @@ -187,41 +188,41 @@ public final class GeoUtils { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_SOUTH_EAST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_E; + return Cell.NSWE_NORTH_EAST; } else { - return GeoStructure.CELL_FLAG_E; + return Cell.NSWE_EAST; } } else if (x < lastX) // west { if (y > lastY) { - return GeoStructure.CELL_FLAG_S & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_SOUTH_WEST; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N & GeoStructure.CELL_FLAG_W; + return Cell.NSWE_NORTH_WEST; } else { - return GeoStructure.CELL_FLAG_W; + return Cell.NSWE_WEST; } } else // unchanged x { if (y > lastY) { - return GeoStructure.CELL_FLAG_S; + return Cell.NSWE_SOUTH; } else if (y < lastY) { - return GeoStructure.CELL_FLAG_N; + return Cell.NSWE_NORTH; } else {